The Problem Composite Solves
A typical integration needs to do several related things: look up an Account, create an Opportunity under it, attach a Product. Three REST calls. Three round trips. Three chances to fail midway.
The Composite API lets you bundle these into one call, with control over whether they run atomically (all or none) and whether later requests can reference earlier ones.
Fewer calls means less latency, fewer API consumption units, and cleaner error handling.
The Four Flavors
Salesforce offers multiple composite endpoints. Each fits different needs.
1. /composite
Multiple sub-requests in one call. Each sub-request can reference output of earlier ones via @{reference} syntax.
Use for orchestration: create parent → create child referencing parent → update the parent’s field.
2. /composite/tree
Create a record and its related records in one call, up to 200 records. Each child references its parent by a client-assigned reference ID.
Use when you’re building parent-child graphs of records (Account + Contacts + Opportunities in one call).
3. /composite/batch
Multiple independent sub-requests executed in parallel. Sub-requests cannot reference each other.
Use when you need to perform several unrelated operations in one call.
4. /composite/sobjects
Bulk create, update, or delete records of a single type. Up to 200 per call. Not for heterogeneous operations.
Use for efficient single-object CRUD at moderate volumes (smaller than Bulk API, larger than single record).
Pattern 1: Create-and-Relate
You’re creating an Opportunity linked to a new Contact linked to an existing Account.
POST /services/data/v60.0/composite
{
"compositeRequest": [
{
"method": "POST",
"url": "/services/data/v60.0/sobjects/Contact",
"referenceId": "NewContact",
"body": {
"FirstName": "John",
"LastName": "Smith",
"AccountId": "001xx000003DGb2"
}
},
{
"method": "POST",
"url": "/services/data/v60.0/sobjects/Opportunity",
"referenceId": "NewOpp",
"body": {
"Name": "Renewal Q4",
"StageName": "Prospecting",
"CloseDate": "2026-12-31",
"ContactId": "@{NewContact.id}",
"AccountId": "001xx000003DGb2"
}
}
]
}
One call. Contact created first; Opportunity references it via @{NewContact.id}.
Pattern 2: Conditional Create (Upsert on External ID)
External systems typically have their own unique IDs. Upsert by external ID to avoid duplicates.
{
"method": "PATCH",
"url": "/services/data/v60.0/sobjects/Account/External_Id__c/ext-12345",
"referenceId": "Acct",
"body": {
"Name": "Acme Corp",
"Industry": "Tech"
}
}
If an Account with External_Id__c = 'ext-12345' exists, update it. Otherwise, create it. Works inside composite requests.
Pattern 3: Transaction Atomicity
Composite supports atomicity via the allOrNone flag. When true, any sub-request failure rolls back all previous sub-requests.
{
"allOrNone": true,
"compositeRequest": [ ... ]
}
Use when the operations are logically one transaction. Without this, partial failures leave inconsistent state.
With atomic mode, be aware: errors surface as “this sub-request failed, all others rolled back.” Error handling in the client must identify which sub-request was the real culprit.
Pattern 4: Bulk Create With Relationships
/composite/tree for creating up to 200 records across related objects in one call.
POST /services/data/v60.0/composite/tree/Account
{
"records": [
{
"attributes": {"type": "Account", "referenceId": "a1"},
"Name": "Acme",
"Contacts": {
"records": [
{
"attributes": {"type": "Contact", "referenceId": "c1"},
"FirstName": "John",
"LastName": "Smith"
}
]
}
}
]
}
Accounts and their Contacts created in one call. Child records automatically link to their parent.
Pattern 5: Parallel Reads
When you need to fetch multiple unrelated records at once, /composite/batch:
POST /services/data/v60.0/composite/batch
{
"batchRequests": [
{"method": "GET", "url": "/services/data/v60.0/sobjects/Account/001xx...A"},
{"method": "GET", "url": "/services/data/v60.0/sobjects/Account/001xx...B"},
{"method": "GET", "url": "/services/data/v60.0/sobjects/Account/001xx...C"}
]
}
Three requests, one round trip. Parallel execution on the server.
Pattern 6: Mixed Operations
Composite allows mixing GET, POST, PATCH, DELETE across sub-requests.
Common shape: GET to verify state, conditional logic on client, then POST/PATCH to update. Caveat: the “conditional logic” runs on your side, not Salesforce’s — so this pattern only works if decisions can be made entirely from request structure, not dynamic data.
For truly conditional-on-server logic, wrap in Apex and call Apex REST.
Request and Response Shape Tips
Keep references descriptive. @{newAccount.id} over @{0.id} — the former is readable.
Check response status per sub-request. The outer HTTP status is 200 even if individual sub-requests fail. You must iterate through the response body.
Request limits: max 25 sub-requests per composite call, 5 times the limit for /composite/batch. Up to 200 records per tree or sobjects call.
API call consumption: each composite request counts as one API call plus one per sub-request. A composite with 20 sub-requests is 21 API calls against your daily limit.
Error Handling Strategy
With allOrNone: false (default), failures in one sub-request don’t abort the rest. Each sub-request’s response includes its own status.
Client-side handling:
- Read each sub-request’s response.
- Log or surface individual failures.
- Retry failed operations independently if appropriate.
With allOrNone: true, the whole response is a failure. Parse the first error to identify what triggered the rollback.
Composite + Bulk: Which When
- 1–200 records, single object, no relationships:
/composite/sobjects. - 1–200 records, parent-child graphs:
/composite/tree. - Mixed operations, some referencing others:
/composite. - Independent parallel requests:
/composite/batch. - 200–10,000s of records, single operation type: Bulk API 2.0, not Composite.
Bulk API 2.0 is fire-and-forget with job polling. Composite is synchronous request/response. Different ergonomics, different use cases.
Anti-Patterns
Stuffing a hundred unrelated operations into one composite call. You hit the sub-request limit and lose readability. Split into multiple composites or use batch.
Using composite for bulk load. Bulk API handles higher volumes more efficiently.
Ignoring atomicity when you need it. A create-then-update sequence without allOrNone leaves inconsistent state on partial failures. Set the flag.
Composite for a single operation. If you have one REST call to make, just make it. Composite adds overhead for no benefit below 2 sub-requests.
Frequently Asked Questions
Does composite respect user permissions?
Yes. Each sub-request runs as the authenticated user. Field-level security, sharing, and validation rules all apply.
Can composite call Apex REST endpoints?
Yes. Any REST URL your user can access works as a sub-request — including custom Apex REST classes.
What’s the timeout?
Standard REST timeouts apply — up to 120 seconds. Long-running aggregate work should use Bulk API or async Apex.
Is there a size limit?
Request body up to 16 MB. Responses can also be large; budget for JSON parsing on the client.