For Zoho Services only:


I'm actually part of something bigger at Ascent Business Solutions recognized as the top Zoho Premium Solutions Partner in the United Kingdom.

Ascent Business Solutions offer support for smaller technical fixes and projects for larger developments, such as migrating to a ZohoCRM.  A team rather than a one-man-band is always available to ensure seamless progress and address any concerns. You'll find our competitive support rates with flexible, no-expiration bundles at http://ascentbusiness.co.uk/zoho-support-2.  For larger projects, check our bespoke pricing structure and receive dedicated support from our hands-on project consultants and developers at http://ascentbusiness.co.uk/crm-solutions/zoho-crm-packages-prices.

The team I manage specializes in coding API integrations between Zoho and third-party finance/commerce suites such as Xero, Shopify, WooCommerce, and eBay; to name but a few.  Our passion lies in creating innovative solutions where others have fallen short as well as working with new businesses, new sectors, and new ideas.  Our success is measured by the growth and ROI we deliver for clients, such as transforming a garden shed hobby into a 250k monthly turnover operation or generating a +60% return in just three days after launch through online payments and a streamlined e-commerce solution, replacing a paper-based system.

If you're looking for a partner who can help you drive growth and success, we'd love to work with you.  You can reach out to us on 0121 392 8140 (UK) or info@ascentbusiness.co.uk.  You can also visit our website at http://ascentbusiness.co.uk.

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:
copyraw
//
// 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);
  1.  // 
  2.  // init 
  3.  l_CrmLineItems = List()
  4.  // 
  5.  // some sample values 
  6.  v_CrmProductID = "123456789012345678"
  7.  // 
  8.  // build up product details JSON to send 
  9.  m_LineItemProduct = Map()
  10.  m_LineItemProduct.put("id",v_CrmProductID)
  11.  m_LineItem = Map()
  12.  m_LineItem.put("product",m_LineItemProduct)
  13.  m_LineItem.put("quantity",5)
  14.  m_LineItem.put("list_price",0.99)
  15.  v_LineItemTax = 0.99 * 5 * 0.2
  16.  m_LineItem.put("Tax",v_LineItemTax)
  17.  if(v_LineItemTax>0) 
  18.  { 
  19.      l_TaxOptions = List()
  20.      m_TaxOption1 = Map()
  21.      m_TaxOption1.put("percentage",20)
  22.      m_TaxOption1.put("name","Sales Tax")
  23.      m_TaxOption1.put("value",v_LineItemTax)
  24.      l_TaxOptions.add(m_TaxOption1)
  25.      m_LineItem.put("line_tax",l_TaxOptions)
  26.  } 
  27.  m_LineItem.put("total",5.94)
  28.  m_LineItem.put("product_description","My Test Description")
  29.  l_CrmLineItems.add(m_LineItem)
  30.  // 
  31.  m_CreateQuote = Map()
  32.  m_CreateQuote.put("Subject","My Test Quote")
  33.  m_CreateQuote.put("Product_Details",l_CrmLineItems)
Which when converted and sent in JSON would look something like the following request:
copyraw
{
  "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
      }
      ]
    }
  ]
}
  1.  { 
  2.    "Subject": "My Test Quote", 
  3.    "Product_Details": [ 
  4.      { 
  5.        "product": { 
  6.          "Product_Code": "TEST1", 
  7.          "Currency": "GBP", 
  8.          "name": "My Test Product", 
  9.          "id": "123456789012345678" 
  10.        }, 
  11.        "quantity": 5, 
  12.        "Discount": 0, 
  13.        "Tax": 0.99, 
  14.        "list_price": 0.99, 
  15.        "total": 5.94, 
  16.        "product_description": "My Test Description", 
  17.        "line_tax": [ 
  18.        { 
  19.          "percentage": 20, 
  20.          "name": "Sales Tax", 
  21.          "value": 0.99 
  22.        } 
  23.        ] 
  24.      } 
  25.    ] 
  26.  } 

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"):
copyraw
//
// 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("trigger",[]);
//
// 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"
];
  1.  // 
  2.  // init 
  3.  l_CrmLineItems = List()
  4.  // 
  5.  // some sample values 
  6.  v_CrmProductID = "123456789012345678"
  7.  // 
  8.  // build up product details JSON to send 
  9.  m_LineItem = Map()
  10.  m_LineItem.put("Product_Name",v_CrmProductID)
  11.  m_LineItem.put("Quantity",5)
  12.  m_LineItem.put("List_Price",0.99)
  13.  // setting a tax as an example 
  14.  v_LineItemTax = 0.99 * 5 * 0.2
  15.  m_LineItem.put("Tax",v_LineItemTax)
  16.  l_TaxOptions = List()
  17.  if(v_LineItemTax>0) 
  18.  { 
  19.      m_TaxOption1 = Map()
  20.      m_TaxOption1.put("percentage",20)
  21.      m_TaxOption1.put("name","Sales Tax")
  22.      m_TaxOption1.put("value",v_LineItemTax)
  23.      l_TaxOptions.add(m_TaxOption1)
  24.  } 
  25.  else 
  26.  { 
  27.      m_TaxOption0 = Map()
  28.      m_TaxOption0.put("percentage",0)
  29.      m_TaxOption0.put("name","Zero Tax")
  30.      m_TaxOption0.put("value",0)
  31.      l_TaxOptions.add(m_TaxOption0)
  32.  } 
  33.  m_LineItem.put("Line_Tax",l_TaxOptions)
  34.  m_LineItem.put("Discount",0)
  35.  m_LineItem.put("Description","My Test Description")
  36.  m_LineItem.put("Grouping","My Test Group")
  37.  l_CrmLineItems.add(m_LineItem)
  38.  // 
  39.  m_CreateQuote = Map()
  40.  m_CreateQuote.put("Subject","My Test Quote")
  41.  m_CreateQuote.put("Quoted_Items",l_CrmLineItems)
  42.  // 
  43.  // send to CRM 
  44.  l_RecordsToSend = List()
  45.  l_RecordsToSend.add(m_CreateQuote)
  46.  m_Data = Map()
  47.  m_Data.put("data", l_RecordsToSend)
  48.  // 
  49.  // this is REST API so by default all triggers run.  Use an empty list to stop or prevent these from triggering. 
  50.  m_Data.put("trigger",[])
  51.  // 
  52.  // send via REST API v2.1 on EU datacenter 
  53.  r_CreateCrmQuote = invokeUrl 
  54.  [ 
  55.      url :"https://www.zohoapis.eu/crm/v2.1/Quotes" 
  56.      type :POST 
  57.      parameters:m_Data.toString() 
  58.      connection:"myconnection" 
  59.  ]
or if converted to JSON, the request would read as:
copyraw
{
  "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
            }
          ]
      }
    ]
  },
  "trigger": []
}
  1.  { 
  2.    "data": { 
  3.      "Subject": "My Test Quote", 
  4.      "Quoted_Items": [ 
  5.        { 
  6.          "Product_Name": "123456789012345678", 
  7.          "Quantity": 5, 
  8.          "Discount": 0, 
  9.          "Tax": 0.99, 
  10.          "List_Price": 0.99, 
  11.          "Total": 5.94, 
  12.          "Description": "My Test Description", 
  13.          "Grouping": "My Test Group", 
  14.          "Line_Tax": [ 
  15.              { 
  16.                "percentage": 20, 
  17.                "name": "Sales Tax", 
  18.                "value": 0.99 
  19.              } 
  20.            ] 
  21.        } 
  22.      ] 
  23.    }, 
  24.    "trigger": [] 
  25.  } 

Reading a Response
The response on success would be something like:
copyraw
{
  "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"
    }
  ]
}
  1.  { 
  2.    "data": [ 
  3.      { 
  4.        "code": "SUCCESS", 
  5.        "details": { 
  6.          "Modified_Time": "2021-05-17T11:02:59+01:00", 
  7.          "Modified_By": { 
  8.            "name": "Joel Admin", 
  9.            "id": "123456" 
  10.          }, 
  11.          "Created_Time": "2021-05-17T11:02:59+01:00", 
  12.          "id": "98498465735491156", 
  13.          "Created_By": { 
  14.            "name": "Joel Admin", 
  15.            "id": "123456" 
  16.          } 
  17.        }, 
  18.        "message": "record added", 
  19.        "status": "success" 
  20.      } 
  21.    ] 
  22.  } 
and if using code to capture the ID:
copyraw
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");
		}
	}
}
  1.  v_ResultingQuoteID = 0
  2.  if(r_CreateCrmQuote.toMap().get("data") != null) 
  3.  { 
  4.      if(r_CreateCrmQuote.toMap().get("data").get(0) != null) 
  5.      { 
  6.          if(r_CreateCrmQuote.toMap().get("data").get(0).get("details") != null) 
  7.          { 
  8.              v_ResultingQuoteID = r_CreateCrmQuote.toMap().get("data").get(0).get("details").get("id")
  9.          } 
  10.      } 
  11.  } 

Retrieving a record with 2.1 JSON
The following is a snippet when querying a CRM module (invoices) from a CRM function. The important fields I wanted were some custom fields within the line items subform. Here I'm adding the parameters to ensure we get CRM records that are also pending approval or being converted.
copyraw
m_Params = Map();
m_Params.put("approved","both");
m_Params.put("converted","both");
r_InvoiceDetails = invokeurl
[
	url :"https://www.zohoapis.eu/crm/v2.1/Invoices/" + p_InvoiceID
	type :GET
	parameters: m_Params
	connection:"joels_connector"
];
info r_InvoiceDetails;
  1.  m_Params = Map()
  2.  m_Params.put("approved","both")
  3.  m_Params.put("converted","both")
  4.  r_InvoiceDetails = invokeUrl 
  5.  [ 
  6.      url :"https://www.zohoapis.eu/crm/v2.1/Invoices/" + p_InvoiceID 
  7.      type :GET 
  8.      parameters: m_Params 
  9.      connection:"joels_connector" 
  10.  ]
  11.  info r_InvoiceDetails; 

Common Error(s):
  • {"code":"MANDATORY_NOT_FOUND","details":{"api_name":"data"},"message":"required field not found","status":"error"} is because you did this:
    copyraw
    r_CreateCrmQuote = invokeurl
    [
    	url :"https://www.zohoapis.eu/crm/v2.1/Quotes"
    	type :POST
    	parameters: m_CreateQuote
    	connection:"myconnection"
    ];
    1.  r_CreateCrmQuote = invokeUrl 
    2.  [ 
    3.      url :"https://www.zohoapis.eu/crm/v2.1/Quotes" 
    4.      type :POST 
    5.      parameters: m_CreateQuote 
    6.      connection:"myconnection" 
    7.  ]
    when you should have done this (send the payload as the value to the key "data"):
    copyraw
    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"
    ];
    1.  l_RecordsToSend = List()
    2.  l_RecordsToSend.add(m_CreateQuote)
    3.  m_Data = Map()
    4.  m_Data.put("data", l_RecordsToSend)
    5.  r_CreateCrmQuote = invokeUrl 
    6.  [ 
    7.      url :"https://www.zohoapis.eu/crm/v2.1/Quotes" 
    8.      type :POST 
    9.      parameters: m_Data.toString() 
    10.      connection:"myconnection" 
    11.  ]

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

  • {"code":"INVALID_DATA","details":{"expected_data_type":"jsonarray","api_name":"data"},"message":"invalid data","status":"error"} is because you did this:
    copyraw
    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() 
    ];
    1.  m_Data = Map()
    2.  m_Data.put("data",m_RecordData)
    3.  r_RecordUpdate = invokeUrl 
    4.  [ 
    5.      url :"https://www.zohoapis.eu/crm/v2.1/Quotes" 
    6.      type :PUT 
    7.      headers: m_Header 
    8.      parameters:m_Data.toString() 
    9.  ]
    when you should have done this (note that a list is submitted to data / could use .toJSONList()):
    copyraw
    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() 
    ];
    1.  l_Records = List()
    2.  l_Records.add(m_RecordData)
    3.  m_Data = Map()
    4.  m_Data.put("data",l_Records)
    5.  r_RecordUpdate = invokeUrl 
    6.  [ 
    7.      url :"https://www.zohoapis.eu/crm/v2.1/Quotes" 
    8.      type :PUT 
    9.      headers: m_Header 
    10.      parameters:m_Data.toString() 
    11.  ]

  • {"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.
Category: Zoho :: Article: 744

Credit where Credit is Due:


Feel free to copy, redistribute and share this information. All that we ask is that you attribute credit and possibly even a link back to this website as it really helps in our search engine rankings.

Disclaimer: Please note that the information provided on this website is intended for informational purposes only and does not represent a warranty. The opinions expressed are those of the author only. We recommend testing any solutions in a development environment before implementing them in production. The articles are based on our good faith efforts and were current at the time of writing, reflecting our practical experience in a commercial setting.

Thank you for visiting and, as always, we hope this website was of some use to you!

Kind Regards,

Joel Lipman
www.joellipman.com

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 bc1qf6elrdxc968h0k673l2djc9wrpazhqtxw8qqp4

Ethereum:
Donate to Joel Lipman with Ethereum 0xb038962F3809b425D661EF5D22294Cf45E02FebF
© 2024 Joel Lipman .com. All Rights Reserved.