Zoho CRM: Update a custom field in line items / product details using REST API v2.1

What?
This is a quick article documenting how to update custom fields in a line items or product details section of a transactional module such as Quotes, Sales Orders or Invoices using code: Zoho Deluge.

Why?
At time of print, Zoho had recently introduced the ability to have custom fields in your line items, alongside the product name, list price, quantity, tax, etc. In the example below, we have added a column called "Group Name" in the CRM Quote module as per the following screenshot:
Populating Custom Field in a Quote Line Item

How?
Again at the time of this article, this is only modifiable when using REST API v2.1. We are going to update the field with label "Group Name" but API name "Grouping" in a list item column.

The standard code you used to use even for REST API v2.0 would have been something like the following:
//
// init
l_CrmLineItems = List();
//
// some sample values
v_CrmProductID = "123456789012345678";
//
// build up product details JSON to send
m_LineItemProduct = Map();
m_LineItemProduct.put("id",v_CrmProductID);
m_LineItem = Map();
m_LineItem.put("product",m_LineItemProduct);
m_LineItem.put("quantity",5);
m_LineItem.put("list_price",0.99);
v_LineItemTax = 0.99 * 5 * 0.2;
m_LineItem.put("Tax",v_LineItemTax);
if(v_LineItemTax>0)
{
	l_TaxOptions = List();
	m_TaxOption1 = Map();
	m_TaxOption1.put("percentage",20);
	m_TaxOption1.put("name","Sales Tax");
	m_TaxOption1.put("value",v_LineItemTax);
	l_TaxOptions.add(m_TaxOption1);
	m_LineItem.put("line_tax",l_TaxOptions);
}
m_LineItem.put("total",5.94);
m_LineItem.put("product_description","My Test Description");
l_CrmLineItems.add(m_LineItem);
//
m_CreateQuote = Map();
m_CreateQuote.put("Subject","My Test Quote");
m_CreateQuote.put("Product_Details",l_CrmLineItems);
Which when converted and sent in JSON would look something like the following request:
{
  "Subject": "My Test Quote",
  "Product_Details": [
    {
      "product": {
        "Product_Code": "TEST1",
        "Currency": "GBP",
        "name": "My Test Product",
        "id": "123456789012345678"
      },
      "quantity": 5,
      "Discount": 0,
      "Tax": 0.99,
      "list_price": 0.99,
      "total": 5.94,
      "product_description": "My Test Description",
      "line_tax": [
      {
        "percentage": 20,
        "name": "Sales Tax",
        "value": 0.99
      }
      ]
    }
  ]
}

Updating with REST API v2.1
The new process doesn't just require you to add in this custom field per line item, but also requires a change to the other fields in the line item. There is an additional note that when using v2.1, you no longer post to the "Product_Details" key but to the respective module line item API name as per the following screenshot:
Populating Custom Field in a Quote Line Item - Referring to API names

Following the API names in this example, the Deluge code to send would be something like the following instead (reminder: instead of Product_Details, post to the API name in this case "Quoted_Items"):
//
// init
l_CrmLineItems = List();
//
// some sample values
v_CrmProductID = "123456789012345678";
//
// build up product details JSON to send
m_LineItem = Map();
m_LineItem.put("Product_Name",v_CrmProductID);
m_LineItem.put("Quantity",5);
m_LineItem.put("List_Price",0.99);
// setting a tax as an example
v_LineItemTax = 0.99 * 5 * 0.2;
m_LineItem.put("Tax",v_LineItemTax);
l_TaxOptions = List();
if(v_LineItemTax>0)
{
	m_TaxOption1 = Map();
	m_TaxOption1.put("percentage",20);
	m_TaxOption1.put("name","Sales Tax");
	m_TaxOption1.put("value",v_LineItemTax);
	l_TaxOptions.add(m_TaxOption1);
}
else
{
	m_TaxOption0 = Map();
	m_TaxOption0.put("percentage",0);
	m_TaxOption0.put("name","Zero Tax");
	m_TaxOption0.put("value",0);
	l_TaxOptions.add(m_TaxOption0);
}
m_LineItem.put("Line_Tax",l_TaxOptions);
m_LineItem.put("Discount",0);
m_LineItem.put("Description","My Test Description");
m_LineItem.put("Grouping","My Test Group");
l_CrmLineItems.add(m_LineItem);
//
m_CreateQuote = Map();
m_CreateQuote.put("Subject","My Test Quote");
m_CreateQuote.put("Quoted_Items",l_CrmLineItems);
//
// send to CRM
l_RecordsToSend = List();
l_RecordsToSend.add(m_CreateQuote);
m_Data = Map();
m_Data.put("data", l_RecordsToSend);
//
// this is REST API so by default all triggers run.  Use an empty list to stop or prevent these from triggering.
m_Data.put("triggers",[]);
//
// send via REST API v2.1 on EU datacenter
r_CreateCrmQuote = invokeurl
[
	url :"https://www.zohoapis.eu/crm/v2.1/Quotes"
	type :POST
	parameters:m_Data.toString()
	connection:"myconnection"
];
or if converted to JSON, the request would read as:
{
  "data": {
    "Subject": "My Test Quote",
    "Quoted_Items": [
      {
        "Product_Name": "123456789012345678",
        "Quantity": 5,
        "Discount": 0,
        "Tax": 0.99,
        "List_Price": 0.99,
        "Total": 5.94,
        "Description": "My Test Description",
        "Grouping": "My Test Group",
        "Line_Tax": [
            {
              "percentage": 20,
              "name": "Sales Tax",
              "value": 0.99
            }
          ]
      }
    ]
  },
  "triggers": []
}

Reading a Response
The response on success would be something like:
{
  "data": [
    {
      "code": "SUCCESS",
      "details": {
        "Modified_Time": "2021-05-17T11:02:59+01:00",
        "Modified_By": {
          "name": "Joel Admin",
          "id": "123456"
        },
        "Created_Time": "2021-05-17T11:02:59+01:00",
        "id": "98498465735491156",
        "Created_By": {
          "name": "Joel Admin",
          "id": "123456"
        }
      },
      "message": "record added",
      "status": "success"
    }
  ]
}
and if using code to capture the ID:
v_ResultingQuoteID = 0;
if(r_CreateCrmQuote.toMap().get("data") != null)
{
	if(r_CreateCrmQuote.toMap().get("data").get(0) != null)
	{
		if(r_CreateCrmQuote.toMap().get("data").get(0).get("details") != null)
		{
			v_ResultingQuoteID = r_CreateCrmQuote.toMap().get("data").get(0).get("details").get("id");
		}
	}
}

Common Error(s):
  • {"code":"MANDATORY_NOT_FOUND","details":{"api_name":"data"},"message":"required field not found","status":"error"} is because you did this:
    r_CreateCrmQuote = invokeurl
    [
    	url :"https://www.zohoapis.eu/crm/v2.1/Quotes"
    	type :POST
    	parameters: m_CreateQuote
    	connection:"myconnection"
    ];
    
    when you should have done this (send the payload as the value to the key "data"):
    l_RecordsToSend = List();
    l_RecordsToSend.add(m_CreateQuote);
    m_Data = Map();
    m_Data.put("data", l_RecordsToSend);
    r_CreateCrmQuote = invokeurl
    [
    	url :"https://www.zohoapis.eu/crm/v2.1/Quotes"
    	type :POST
    	parameters: m_Data.toString()
    	connection:"myconnection"
    ];
    

  • {"code":"INVALID_DATA","details":{"expected_data_type":"jsonobject"},"message":"body","status":"error"} is because you did this:
    r_CreateCrmQuote = invokeurl
    [
    	url :"https://www.zohoapis.eu/crm/v2.1/Quotes"
    	type :POST
    	parameters:m_Data
    	connection:"myconnection"
    ];
    
    when you should have done this (note the .toString()):
    r_CreateCrmQuote = invokeurl
    [
    	url :"https://www.zohoapis.eu/crm/v2.1/Quotes"
    	type :POST
    	parameters:m_Data.toString()
    	connection:"myconnection"
    ];
    

  • {"code":"INVALID_DATA","details":{"expected_data_type":"jsonarray","api_name":"data"},"message":"invalid data","status":"error"} is because you did this:
    m_Data = Map();
    m_Data.put("data",m_RecordData);
    r_RecordUpdate = invokeUrl
    [
    	url :"https://www.zohoapis.eu/crm/v2.1/Quotes"
    	type :PUT
    	headers: m_Header
    	parameters:m_Data.toString() 
    ];
    
    when you should have done this (note that a list is submitted to data / could use .toJSONList()):
    l_Records = List();
    l_Records.add(m_RecordData);
    m_Data = Map();
    m_Data.put("data",l_Records);
    r_RecordUpdate = invokeUrl
    [
    	url :"https://www.zohoapis.eu/crm/v2.1/Quotes"
    	type :PUT
    	headers: m_Header
    	parameters:m_Data.toString() 
    ];
    

  • {"code":"INVALID_TOKEN","details":{},"message":"invalid oauth token","status":"error"} is probably because you are on the .COM datacenter and you should be using https://www.zohoapis.com/crm/v2.1/Quotes but if you were on the EU datacenter, you would use https://www.zohoapis.eu/crm/v2.1/Quotes.
  • Line Item Tax does not submit in API v2.1 Trial and error resolved this one. Submit the line item but instead of "line_tax", replace this with "Line_Tax" (note the capitalized letters). I have included this in the example above.
  • Line Item Tax doesn't display at line item lvel Previously submitting no tax would still put a 0.00 in the tax column of a line item. I have found that you have to include the line tax option of zero tax (or whatever you call zero rated tax) in the line item. Again this is included in my v2.1 example above. Should be submitted even if tax at line item level is 0.
  • Triggers when using REST API v2.1 When using the REST API (even v2.0), the workflow/approval/blueprint triggers will all run by default. Set this to an empty list [] to prevent/stop CRM triggers from running any workflows.

Related Articles

Joes Revolver Map

Accreditation

Badge - Certified Zoho Creator Associate
Badge - Certified Zoho Creator Associate

Donate & Support

If you like my content, and would like to support this sharing site, feel free to donate using a method below:

Paypal:
Donate to Joel Lipman via PayPal

Bitcoin:
Donate to Joel Lipman with Bitcoin - Valid till 8 May 2022 3QnhmaBX7LQSRsC9hh6Je9rGQKEGNQNfPb
© 2021 Joel Lipman .com. All Rights Reserved.