When the quote is approved in Creator, the invoice should already exist in Books. This function finds the customer (creating them if this is their first order), turns a Creator subform into invoice line items, and raises the invoice — writing the invoice number back so the two records stay linked.
The original cannot authenticate any more. Zoho retired legacy authtokens entirely — deprecation was announced in December 2020 and every existing token stopped working on 1 April 2021. Any script still passing ?authtoken= has been failing for years. Everything now goes through OAuth 2.0 with a named Zoho connection. The legacy getUrl() and postUrl() tasks go with it — use invokeurl or the native integration tasks. The original also posted to books.zoho.com/api/v3, which is superseded by www.zohoapis.com/books/v3, and wrapped its payload in the old JSONString= form-encoding. All three are replaced below by the native zoho.books.* tasks, which handle the host, the auth and the encoding for you.
zoho_books_connection with ZohoBooks.contacts.CREATE, ZohoBooks.contacts.READ and ZohoBooks.invoices.CREATE.Invoice (with an Item_Detail subform), Contact with a Zoho_Books_Contact_ID field, and Item with Zoho_Books_Item_ID.// ============================================
// CREATOR -> ZOHO BOOKS / INVOICE
// Find or create the contact, then raise the invoice.
// ============================================
void ZohoBooks.createInvoice(int recordId)
{
ORG_ID = "YOUR_BOOKS_ORG_ID";
CONN = "zoho_books_connection";
rec = Invoice[ID == recordId];
customer = Contact[ID == rec.Contact];
// --- 1. Reuse the stored contact id, or create the contact ---
contactId = customer.Zoho_Books_Contact_ID;
if(contactId == null || contactId == "")
{
newContact = Map();
newContact.put("contact_name",customer.Company_Name);
newContact.put("contact_type","customer");
newContact.put("email",customer.Email);
createResp = zoho.books.createRecord("contacts",ORG_ID,newContact,CONN);
contactBlock = createResp.get("contact");
if(contactBlock == null)
{
info "Books contact creation failed: " + createResp.toString();
return;
}
contactId = contactBlock.get("contact_id");
customer.Zoho_Books_Contact_ID = contactId;
}
// --- 2. Build the line items from the Creator subform ---
lineItems = List();
for each row in rec.Item_Detail
{
itemRec = Item[ID == row.Item_Name];
lineItem = Map();
lineItem.put("item_id",itemRec.Zoho_Books_Item_ID);
lineItem.put("name",itemRec.Item_Name);
lineItem.put("rate",row.Rate);
lineItem.put("quantity",row.Quantity);
lineItems.add(lineItem);
}
if(lineItems.size() == 0)
{
info "No line items on record " + recordId + " - nothing to invoice.";
return;
}
// --- 3. Raise the invoice ---
invoiceMap = Map();
invoiceMap.put("customer_id",contactId);
invoiceMap.put("line_items",lineItems);
invoiceMap.put("date",zoho.currentdate.toString("yyyy-MM-dd"));
invoiceMap.put("due_date",zoho.currentdate.addDay(30).toString("yyyy-MM-dd"));
invoiceMap.put("reference_number","CR-" + recordId);
invoiceResp = zoho.books.createRecord("invoices",ORG_ID,invoiceMap,CONN);
invoiceBlock = invoiceResp.get("invoice");
if(invoiceBlock != null)
{
rec.Books_Invoice_ID = invoiceBlock.get("invoice_id");
rec.Books_Invoice_Number = invoiceBlock.get("invoice_number");
info "Invoice created: " + rec.Books_Invoice_Number;
}
else
{
info "Books invoice creation failed: " + invoiceResp.toString();
}
}contact_id to the Creator record the first time and reuse it.zoho.books.createRecord for zoho.invoice.create and the scopes for ZohoInvoice.*. Note the Invoice task is create, not createRecord, and its connection argument is not supported from Creator — Creator uses the app’s built-in Zoho Invoice integration instead..get("invoice") and testing for null is more reliable than searching the raw response for a substring.This script is part of the free Creator Scripts Deluge Library.
All 39 Deluge scripts, the full Zoho Creator course, and every downloadable asset are now free. Get free access →