Composite APIs
Some notes about using the /composite API to do up to 25 statements in a single API call.
- Composite API resources
- https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_composite.htm
- Technical video https://youtu.be/R6eVlc3Dwco?t=2416
- Full technical documentation https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/requests_composite.htm
- Notes about using the Composite API
- This is a new feature from Salesforce, and it saves a significant number of API calls into Salesforce.
- You can have up to 25 REST API statements in one API call.
- NOTE: The API code can not have any conditional statements in it - it’s just one block with all of the records you want to query / create or update structured as a block of JSON then sent over to Salesforce.
- So for example for multiple Order Line Items, you will loop through the list of Line Items in your PHP code for example, and produce the JSON that has the 2 (+) Invoice Line Item POST statements
- If any one of the items fails the whole lot will fail.
I am using this to do an integration between a Website and Salesforce. The client is an NFP and doesn't have budget to get a dev to to Custom Apex REST.
So, I read the docco and did a nice piece of pseudo code referencing all fields in Salesforce and matching it up with the fields in the Website, I thought it was going to work like a charm.
My pseudo code (yes, with all the errors in it)
path = "/services/data/42.0" // On Update of an existing Contact's details or creation of a new Contact //If SFContactID is blank { "method" : "PATCH", "url" : path + "/sobjects/Account/UniqueID__c/ABCEnterprisesNSW", //Company Name+(Billing Address State or Other State) with spaces stripped out then trimmed to 255 chars "referenceID" : "refNewAccount", "body" : { "Name" : "ABC Enterprises", //Company "BillingStreet" : "15 Smith Street", //Billing Address - Street "BillingCity" : "Smithville", //Billing Address - Suburb "BillingState" : "NSW", //Billing Address - State or Other State "BillingPostalCode" : "2656", //Billing Address - Postcode "BillingCountry" : "Australia", //Billing Address Country "Phone" : "02 9898 9898" //Business Phone } }, //Now get that Account ID just created or updated { "method" : "GET", "url" : path + "/sobjects/Account/@{refNewAccount.id}", "referenceId" : "refNewAccountInfo" }, // Now Create or Update the Contact { "method" : "PATCH", "url" : path + "/sobjects/Contact/UniqueID__c/JaneDoejdoe@gmail.com", //First Name + Surname + Email Address with spaces striped out then trimmed to 255 chars "referenceId" : "refNewContact", "body" : { "Saluation" : "Ms", //Title "FirstName" : "Jane", //First Name "LastName" : "Doe", //Surname "Title" : "Director", //Position "Email" : "jdoe@gmail.com", //Email Address "AccountId" : "@{NewAccountInfo.Id}", "MailingStreet" : "@{NewAccountInfo.BillingStreet}", "MailingCity" : "@{NewAccountInfo.BillingCity}", "MailingState" : "@{NewAccountInfo.BillingState}", "MailingPostalCode" : "@{NewAccountInfo.BillingPostalCode}", "MailingCountry" : "@{NewAccountInfo.BillingCountry}", "Phone" : "@{NewAccountInfo.Phone}", "MobilePhone" : "9908960940" //Mobile } }, //Now get that Contact ID just created or updated { "method" : "GET", "url" : path + "/sobjects/Contact/@{refNewContact.id}", "referenceId" : "refContactInfo" }, // Update that into your SFContactID field on the website
Project go ahead happens and the first thing I do is fix up my pseudo code to test it in Workbench. Oops, I got so much wrong - apart from the spelling mistakes like 'Saluation' rather than 'Salutation'.
Gotchas
- referenceId must be spelt referenceId, not referenceID
- Doing a PATCH on Account does not return the referenceId if it does not create a new Account. This seems weird to me, but I'm going to have to run with it for now.
- The refNewAccount.id will not work if the Account is updated, only if it is created.
"method" : "PATCH", "url" : "/services/data/v41.0/sobjects/Account/UniqueID__c/ABCEnterprisesVIC", "referenceId" : "refNewAccount", "body" : { "Name" : "ABC Enterprises", "BillingStreet" : "15 Smith Street", "BillingCity" : "Smithville", "BillingState" : "VIC", "BillingPostalCode" : "2656", "BillingCountry" : "Australia", "Phone" : "02 9898 9898", } }, { "method" : "PATCH", "url" : "/services/data/v41.0/sobjects/Contact/UniqueID__c/JaneDoejdoe@gmail.com", "referenceId" : "refNewContact", "body" : { "Salutation" : "Ms", "FirstName" : "Jane", "LastName" : "Doe", "Title" : "Director", "Email" : "jdoe@gmail.com", "AccountId" : "@{refNewAccount.id}" }
- Doing a GET on an id returns the whole object, not just the ID
- "method" : "GET",
"url" : "/services/data/v41.0/sobjects/Contact/@{refNewContact.id}",
"referenceId" : "refContactInfo"
- "method" : "GET",
- Check out the gotcha at the bottom of this page about the case of the id field https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/requests_composite.htm
Solution
- So you have to do a GET with a query in the middle of the two PATCH methods.
- AND you can't use {{@{refNewAccount.id}}} syntax, you have to use the extended query syntax.
- So this is what finally worked!
{ "compositeRequest" : [ { "method" : "PATCH", "url" : "/services/data/v41.0/sobjects/Account/UniqueID__c/ABCEnterprisesNSW", "referenceId" : "refNewAccount", "body" : { "Name" : "ABC Enterprises", "BillingStreet" : "15 Smith Street", "BillingCity" : "Smithville", "BillingState" : "NSW", "BillingPostalCode" : "2656", "BillingCountry" : "Australia", "Phone" : "02 9898 9898" } }, { "method" : "GET", "url" : "/services/data/v41.0/query/?q=select+id,name,Industry,BillingStreet,BillingCity,BillingState,BillingPostalCode,BillingCountry,Phone+from+Account+where+UniqueID__c='ABCEnterprisesNSW'", "referenceId" : "refNewAccountInfo" }, { "method" : "PATCH", "url" : "/services/data/v41.0/sobjects/Contact/UniqueID__c/JaneDoejdoe@gmail.com", "referenceId" : "refNewContact", "body" : { "Salutation" : "Ms", "FirstName" : "Jane", "LastName" : "Doe", "Title" : "Director", "Email" : "jdoe@gmail.com", "AccountId" : "@{refNewAccountInfo.records[0].Id}", "MailingStreet" : "@{refNewAccountInfo.records[0].BillingStreet}", "MailingCity" : "@{refNewAccountInfo.records[0].BillingCity}", "MailingState" : "@{refNewAccountInfo.records[0].BillingState}", "MailingPostalCode" : "@{refNewAccountInfo.records[0].BillingPostalCode}", "MailingCountry" : "@{refNewAccountInfo.records[0].BillingCountry}", "Phone" : "@{refNewAccountInfo.records[0].Phone}", "MobilePhone" : "9908960940" } }, { "method" : "GET", "url" : "/services/data/v41.0/query/?q=select+id+from+Contact+where+UniqueID__c='JaneDoejdoe@gmail.com'", "referenceId" : "refNewContactId" }] }
So what that tells us is that once again, Salesforce example code is the simplest code, and you have to go through so many hoops to get even the most basic real world scenario working. But thankfully this post https://developer.salesforce.com/blogs/tech-pubs/2017/01/simplify-your-api-code-with-new-composite-resources.html helped me in the right direction of using the records[0] syntax.
The next thing I tested is "allOrNone" because I don't want the Account created if it can't create the Contact (because they are the one Contact record on the Website - and remember this is an NFP so we use the 1 to 1 model and create an Account and Contact for the one Contact record.
allOrNone works like this:
{ "allOrNone" : true, "compositeRequest" : [ { ... }] }
And returns an error like this
So that looks like it will work well. I've asked the Website developer to email me any time there is an error like this, especially during testing phase.