Insert or Update Records (Upsert)
Purpose
To insert or update a record. The system checks for duplicate records based on the duplicate check field's values. If the record is already present, it gets updated. If not, the record is inserted.
Endpoints
Request Details
Request URL
{api-domain}/crm/{version}/{module_api_name}/upsert
Supported modules
Leads, Accounts, Contacts, Deals, Campaigns, Cases, Solutions, Products, Vendors, Price Books, Quotes, Sales Orders, Purchase Orders, Invoices, and Custom
Header
Authorization: Zoho-oauthtoken d92d4xxxxxxxxxxxxx15f52
If-Unmodified-Since: 2024-01-15T15:26:49+05:30
Scope
scope=ZohoCRM.modules.ALL
(or)
scope=ZohoCRM.modules.{module_name}.{operation_type}
Possible module names
leads, accounts, contacts, deals, campaigns, cases, solutions, products, vendors, pricebooks, quotes, salesorders, purchaseorders, invoices, and custom.
Possible operation types
ALL - Full access to the record
WRITE - Edit records in the module
CREATE - Create records in the module
Sample Request
Copiedcurl "https://www.zohoapis.com/crm/v2.1/Leads/upsert"
-H "Authorization: Zoho-oauthtoken 1000.8cb99dxxxxxxxxxxxxx9be93.9b8xxxxxxxxxxxxxxxf"
-d "@upsertlead.json"
-X POST
Copied//Get instance of RecordOperations Class that takes moduleAPIName as parameter
$recordOperations = new RecordOperations();
//Get instance of BodyWrapper Class that will contain the request body
$request = new BodyWrapper();
//List of Record instances
$records = array();
$recordClass = 'com\zoho\crm\api\record\Record';
//Get instance of Record Class
$record1 = new $recordClass();
/*
* Call addFieldValue method that takes two arguments
* 1 -> Call Field "." and choose the module from the displayed list and press "." and choose the field name from the displayed list.
* 2 -> Value
*/
$field = new Field("");
$record1->addFieldValue(Leads::City(), "City");
$record1->addFieldValue(Leads::LastName(), "Last Name");
$record1->addFieldValue(Leads::FirstName(), "First Name");
$record1->addFieldValue(Leads::Company(), "Company1");
/*
* Call addKeyValue method that takes two arguments
* 1 -> A string that is the Field's API Name
* 2 -> Value
*/
$record1->addKeyValue("Custom_field", "Value");
$record1->addKeyValue("Custom_field_2", "value");
//Add Record instance to the list
array_push($records, $record1);
//Get instance of Record Class
$record2 = new $recordClass();
/*
* Call addFieldValue method that takes two arguments
* 1 -> Call Field "." and choose the module from the displayed list and press "." and choose the field name from the displayed list.
* 2 -> Value
*/
$record2->addFieldValue(Leads::City(), "City");
$record2->addFieldValue(Leads::LastName(), "Last Name");
$record2->addFieldValue(Leads::FirstName(), "First Name");
$record2->addFieldValue(Leads::Company(), "Company12");
/*
* Call addKeyValue method that takes two arguments
* 1 -> A string that is the Field's API Name
* 2 -> Value
*/
$record2->addKeyValue("Custom_field", "Value");
$record2->addKeyValue("Custom_field_2", "value");
//Add Record instance to the list
array_push($records, $record2);
$duplicateCheckFields = array("City", "Last_Name", "First_Name");
$request->setDuplicateCheckFields($duplicateCheckFields);
//Set the list to Records in BodyWrapper instance
$request->setData($records);
//Call createRecords method that takes BodyWrapper instance as parameter.
$response = $recordOperations->upsertRecords($moduleAPIName, $request);
Copied<?php
class UpsertRecords
{
public function execute(){
$curl_pointer = curl_init();
$curl_options = array();
$url = "https://www.zohoapis.com/crm/v2.1/Leads/upsert";
$curl_options[CURLOPT_URL] =$url;
$curl_options[CURLOPT_RETURNTRANSFER] = true;
$curl_options[CURLOPT_HEADER] = 1;
$curl_options[CURLOPT_CUSTOMREQUEST] = "POST";
$requestBody = array();
$recordArray = array();
$recordObject = array();
$recordObject["Company"]="FieldAPIValue";
$recordObject["Last_Name"]="3477061000007420006";
$recordObject["First_Name"]="3477061000007420006";
$recordObject["State"]="FieldAPIValue";
$recordArray[] = $recordObject;
$requestBody["data"] =$recordArray;
$curl_options[CURLOPT_POSTFIELDS]= json_encode($requestBody);
$headersArray = array();
$headersArray[] = "Authorization". ":" . "Zoho-oauthtoken " . "1000.30f3a589XXXXXXXXXXXXXXXXXXX4077.dc5XXXXXXXXXXXXXXXXXXXee9e7c171c";
$curl_options[CURLOPT_HTTPHEADER]=$headersArray;
curl_setopt_array($curl_pointer, $curl_options);
$result = curl_exec($curl_pointer);
$responseInfo = curl_getinfo($curl_pointer);
curl_close($curl_pointer);
list ($headers, $content) = explode("\r\n\r\n", $result, 2);
if(strpos($headers," 100 Continue")!==false){
list( $headers, $content) = explode( "\r\n\r\n", $content , 2);
}
$headerArray = (explode("\r\n", $headers, 50));
$headerMap = array();
foreach ($headerArray as $key) {
if (strpos($key, ":") != false) {
$firstHalf = substr($key, 0, strpos($key, ":"));
$secondHalf = substr($key, strpos($key, ":") + 1);
$headerMap[$firstHalf] = trim($secondHalf);
}
}
$jsonResponse = json_decode($content, true);
if ($jsonResponse == null && $responseInfo['http_code'] != 204) {
list ($headers, $content) = explode("\r\n\r\n", $content, 2);
$jsonResponse = json_decode($content, true);
}
var_dump($headerMap);
var_dump($jsonResponse);
var_dump($responseInfo['http_code']);
}
}
(new UpsertRecords())->execute();
Copied# List to hold Record instances
records = []
# Get instance of Record Class
record = Record::Record.new
# """
# Call add_field_value method that takes two arguments
# 1 -> Call Field "::" and choose the module from the displayed list and press "." and choose the field name from the displayed list.
# 2 -> Value
# """
if module_api_name.downcase == 'Leads'.downcase
record.add_field_value(Record::Field::Leads.Last_name, 'asdad')
record.add_field_value(Record::Field::Leads.City, 'City')
record.add_field_value(Record::Field::Leads.First_name, 'First Name')
record.add_field_value(Record::Field::Leads.Company, 'KKRNP')
end
# file = Record::FileDetails.new
# file.file_id = "f46166fa14ce16c6e2622b3ce82830759c6334275dc8a317539bbda39a6ca056"
# files = [file]
# """
# Call add_key_value method that takes two arguments
# 1 -> A string that is the Field's API Name
# 2 -> Value
# """
if module_api_name == 'Contacts'
file_details = []
file_detail = Record::FileDetails.new
file_detail.file_id = '479f0f5eebf0fb982f99e3832b35d23e29f67c2868ee4c789f22579895383c8'
file_details.push(file_detail)
record.add_key_value('File_Upload_1', file_details)
end
# """
# Following methods are being used only by Inventory modules
# """
if %w[Quotes Sales_Orders Purchase_Orders Invoices].include? module_api_name
line_item_product = Record::LineItemProduct.new
line_item_product.id = 3_477_061_000_005_356_009
inventory_line_item = Record::InventoryLineItems.new
inventory_line_item.product = line_item_product
inventory_line_item.list_price = 10.0
inventory_line_item.discount = '5.0'
inventory_line_item.quantity = 123.2
line_tax = Record::LineTax.new
line_tax.name = 'Tax1'
line_tax.percentage = 20.0
line_taxes = [line_tax]
inventory_line_item.line_tax = line_taxes
inventory_line_items = [inventory_line_item]
record.add_key_value('Product_Details', inventory_line_items)
record.add_key_value('Subject', 'asd')
end
if module_api_name == 'Price_Books'
pricing_detail_record = Record::PricingDetails.new
pricing_detail_record.from_range = 1.0
pricing_detail_record.to_range = 1.0
pricing_detail_record.discount = 1.0
pricing_detail_records = [pricing_detail_record]
record.add_key_value('Price_Book_Name', 'assd')
record.add_field_value(Record::Field::Price_Books.Pricing_details, pricing_detail_records)
record.add_field_value(Record::Field::Price_Books.Pricing_model, Util::Choice.new('Flat'))
end
# # Get instance of BodyWrapper Class that will contain the request body
records.push(record)
trigger = []
trigger.push('approval')
trigger.push('workflow')
trigger.push('blueprint')
# Get instance of BodyWrapper Class that will contain the request body
body_wrapper = Record::BodyWrapper.new
# Set the list to data in BodyWrapper instance
body_wrapper.data = records
# Set the lar_id in BodyWrapper instance
body_wrapper.lar_id = '213123131'
#set trigger
body_wrapper.trigger = trigger
process = ['review_process']
body_wrapper.process = process
# Get instance of RecordOperations Class
rr = Record::RecordOperations.new
# Call upsertRecords method that takes BodyWrapper instance and module_api_name as parameters.
response = rr.upsert_records(module_api_name,body_wrapper)
Copiedrequire 'net/http'
require 'json'
class UpsertRecords
def execute
url ="https://www.zohoapis.com/crm/v2.1/Leads/upsert"
url = URI(url)
req = Net::HTTP::Post.new(url.request_uri)
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
headers={}
headers["Authorization"]="Zoho-oauthtoken 1000.dfa7XXXXXXXXXXXXXXXXXX84f9665840.c176aeXXXXXXXXXXXX13f3d37a84d"
headers&.each { |key, value| req.add_field(key, value) }
request_body = {};
record_array = [];
record_object = {};
record_object["FieldAPIName"] = "FieldAPIValue";
record_object["Company"]="FieldAPIValue";
record_object["Last_Name"]="3477061000007420006";
record_object["First_Name"]="3477061000007420006";
record_object["State"]="FieldAPIValue";
record_array = [record_object];
request_body["data"] =record_array;
request_json = request_body.to_json
req.body = request_json.to_s
response=http.request(req)
status_code = response.code.to_i
headers = response.each_header.to_h
print status_code
print headers
unless response.body.nil?
print response.body
end
end
end
UpsertRecords.new.execute
CopiedSyntax:
<response> = zoho.crm.upsert(<module>,<values>,<optional_data>,<[connection>]);
mandatory : module,dataMap
Sample Request:
resp = zoho.crm.upsert("Leads", {"Last_Name":"Patricia upsert UF2", "UF":"p.boyle@zylker.com", "Email":"d.grogan@zylker.com"}, {"duplicate_check_fields":["UF" , "Email"]});
In the request, "@upsertlead.json" contains the sample input data.
Note
- The system checks for duplicate records based on the duplicate check field's values. There are two types of duplicate check fields:
- The system-defined duplicate check fields - Certain system-generated fields are marked as unique, by default. When you upsert a record, the system checks for duplicate entries in these fields. Refer to System-defined Duplicate Check Fields section below to know the module-wise system-defined duplicate check fields.
- The user-defined unique fields - The fields for which "Do not allow duplicate values" is enabled. Click here to know more.
You can set the order in which the system checks for duplicate records by specifying the duplicate_check_field array in the input. Note that you can add only system-defined duplicate check fields and user-defined unique fields to the duplicate_check_field array.
Example for the Leads module:
"duplicate_check_fields": [
"Email",
"Phone",
"Fax"
]Here, "Email" is the system-defined duplicate check field, and "Phone" and "Fax" are the user-defined unique fields for the Leads module.
- If you do not specify the duplicate_check_fields, the system checks for duplicate records in this order: system-defined duplicate check fields > user-defined unique fields..
- The 'INVALID_DATA' error is thrown if the field value length exceeds the maximum length defined for that field.
- If an API is used inside a Function and the field value length exceeds the limit, then that function receives an error response from the API. For ex: If the max length for a "Text field" is defined as 10, then value given for it in API cannot be "12345678901", since it has 11 characters.
- A maximum of 100 records can be inserted/updated per API call.
- You must use only Field API names in the input. You can obtain the field API names from
- Fields metadata API (the value for the key “api_name” for every field). (Or)
- Setup > Developer Hub > APIs and SDKs > API Names > {Module}. Choose “Fields” from the “Filter By” drop-down.
- The trigger input can be workflow, approval, or blueprint. If the trigger is not mentioned, the workflows, approvals and blueprints related to the API will get executed. Enter the trigger value as [] to not execute the workflows.
- Records with Subform details can also be updated to CRM using the Records API. Please look at Subforms API to learn more about updating subform information within a record.
- Refer to Get Records API to know more about field types and limits.
- The $append_values represents whether you want to append the values of the multi-select picklist you specified in the input to the existing values. Specify the API names of the multi-select picklists and "true" or "false" as key-value pairs in this JSON object. The value "true" adds the values in the input to the multi-select picklist, while the value "false" replaces the existing values with the ones in the input.
- The apply_feature_execution array allows you to apply an existing layout rule when you create or update a record. Layout rules allow you to show a section, certain fields, set mandatory fields, or show a subform. However, you can only trigger the "Set Mandatory Field" option through the API.
Specify the value layout_rules for the key name to apply a layout rule to the record. Note that you must give this key parallel to "data".
Ensure that the input body includes all the keys required to satisfy the criteria in the layout rule. The system throws an error, otherwise. For example, if the layout rule triggers when the Lead_Source is Employee Referral, and you have mandated the Company field, then the system throws the MANDATORY_NOT_FOUND error if you fail to give the Company key in the input. - You must provide the layout ID field if you want to
- include the layout specific mandatory fields in the API-level mandatory check
- include only the fields that are defined in your layout for API- level processing and ignore the rest
System-defined Duplicate Check Fields
Following are the default duplicate check fields for different modules.
Leads - Email, Accounts - Account_Name, Contacts - Email, Deals - Deal_Name, Campaigns - Campaign_Name, Cases - Subject, Solutions - Solution_Title, Products - Product_Name, Vendors - Vendor_Name, PriceBooks - Price_Book_Name, Quotes - Subject, SalesOrders - Subject, PurchaseOrders - Subject, Invoices - Subject, CustomModules - Name
To know the specific details about each field type in Zoho CRM and their limitations, refer to Sample Attributes section in Insert Records API.
How does the duplicate check work?
Consider a case where the user has configured two unique fields unique1 and unique2 for a module (user-defined unique fields), and Email is a system-defined unique field.
The following table explains how the duplicate check happens for different user inputs to the duplicate_check_fields array.
User Input to the "duplicate_check_fields" Array | Duplicate Check Pattern |
---|---|
unique1 | unique1, unique2 |
unique2 | unique2, unique1 |
unique1, unique2 | unique1, unique2 |
Email, unique1, unique2 | |
No input | System-defined duplicate check field for that module followed by unique fields. That is, Email, unique1, unique2 |
If you do not specify system-defined duplicate check fields in the array, the system will ignore them and check only for user-defined unique fields.
Sample Input
Copied{
"data": [
{
"Last_Name": "Lead_changed",
"Email": "newcrmapi@zoho.com",
"Company": "abc",
"Lead_Status": "Contacted",
"Foreign_Languages": [//multi-select picklist
"Korean"
],
"$append_values": {
"Foreign_Languages": false
}
},
{
"Last_Name": "New Lead",
"First_Name": "CRM Lead",
"Email": "newlead@zoho.com",
"Lead_Status": "Attempted to Contact",
"Mobile": "7685635434",
"Foreign_Languages": [//multi-select picklist
"Spanish"
],
"$append_values": {
"Foreign_Languages": true
}
}
],
"duplicate_check_fields": [
"Email",
"Mobile"
],
"trigger": [
"workflow"
],
"lar_id": "3652397000002045001",
"apply_feature_execution": [
{
"name": "layout_rules"
}
]
}
Possible Errors
- INVALID_MODULEHTTP 400
The module name given seems to be invalid
Resolution: You have specified an invalid module name or there is no tab permission, or the module could have been removed from the available modules. Specify a valid module API name. - INVALID_MODULEHTTP 400
The given module is not supported in API
Resolution: The modules such as Documents and Projects are not supported in the current API. (This error will not be shown, once these modules are been supported). Specify a valid module API name. - INVALID_DATAHTTP 400
invalid data
Resolution: The input specified is incorrect. Specify valid input. - INVALID_URL_PATTERNHTTP 404
Please check if the URL trying to access is a correct one
Resolution: The request URL specified is incorrect. Specify a valid request URL. Refer to request URL section above. - OAUTH_SCOPE_MISMATCHHTTP 401
Unauthorized
Resolution: Client does not have ZohoCRM.modules.{module_name}.WRITE/ZohoCRM.modules.{module_name}.CREATE scope. Create a new client with valid scope. Refer to scope section above. - NO_PERMISSIONHTTP 403
Permission denied to upsert
Resolution: The user does not have permission to upsert records. Contact your system administrator. - ALREADY_MODIFIEDHTTP 412
Record updated time has already passed if-unmodified-since time
Resolution: The record has been updated after the specified If-Unmodified-Since timestamp. Try again with a more recent value for the If-Unmodified-Since header. - INTERNAL_ERRORHTTP 500
Internal Server Error
Resolution: Unexpected and unhandled exception in the server. Contact support team. - INVALID_REQUEST_METHODHTTP 400
The http request method type is not a valid one
Resolution: You have specified an invalid HTTP method to access the API URL. Specify a valid request method. Refer to endpoints section above. - AUTHORIZATION_FAILEDHTTP 400
User does not have sufficient privilege to upsert records.
Resolution: The user does not have the permission to upsert record details. Contact your system administrator. - MANDATORY_NOT_FOUNDHTTP 400
required field not found
Resolution: You have not specified one or more mandatory fields in the input. Refer to Fields Metadata API to know the mandatory fields.
Sample Response
Copied{
"data": [
{
"code": "SUCCESS",
"duplicate_field": "Email",
"action": "update",
"details": {
"Modified_Time": "2020-10-14T10:31:43+05:30",
"Modified_By": {
"name": "Patricia Boyle",
"id": "4150868000000225013"
},
"Created_Time": "2019-09-11T16:21:15+05:30",
"id": "4150868000000376008",
"Created_By": {
"name": "Patricia Boyle",
"id": "4150868000000225013"
}
},
"message": "record updated",
"status": "success"
},
{
"code": "SUCCESS",
"duplicate_field": null,
"action": "insert",
"details": {
"Modified_Time": "2020-10-14T10:31:43+05:30",
"Modified_By": {
"name": "Patricia Boyle",
"id": "4150868000000225013"
},
"Created_Time": "2020-10-14T10:31:43+05:30",
"id": "4150868000003194003",
"Created_By": {
"name": "Patricia Boyle",
"id": "4150868000000225013"
}
},
"message": "record added",
"status": "success"
}
]
}