Composite APIs

Some notes about using the /composite API to do up to 25 statements in a single API call. 

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}"
   }


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.