Salesforce Composite API Capabilities & Subrequest Limits
What are the Salesforce Composite API and Composite Graph API subrequest limits and capabilities?
TL;DR
- Bottom line: Salesforce offers four composite resource types — Composite (25 subrequests with reference chaining), Composite Graph (500 subrequests with per-graph transactions), Composite Batch (25 independent subrequests), and sObject Collections (200 records per DML). Choose based on whether you need cross-subrequest references, all-or-nothing behavior, or maximum throughput.
- Key limit: Composite API caps at 25 subrequests (max 5 queries/collections); Composite Graph scales to 500 but each graph is its own transaction. The entire composite call counts as 1 API call against daily limits.
- Watch out for: Governor limits (100 SOQL queries, 150 DML statements, 10,000ms CPU) apply cumulatively across ALL subrequests in a single composite call — exceeding any governor limit aborts the entire request.
- Best for: Multi-step create/update workflows requiring atomicity (e.g., create Account + Contact + Opportunity in one call), or reducing API call consumption when creating related records.
- Authentication: OAuth 2.0 JWT bearer flow for server-to-server; web server flow for user-context. All composite resources require the same auth as standard REST API.
System Profile
This card covers all four Salesforce REST API composite resource types: Composite, Composite Graph, Composite Batch, and sObject Collections/Tree. These resources allow combining multiple REST API operations into a single HTTP call, reducing round trips and API call consumption. Available in API v43.0+ for sObject Collections and v50.0+ for Composite Graph. All editions that support REST API support composite resources, but API call daily limits vary significantly by edition.
| Property | Value |
|---|---|
| Vendor | Salesforce |
| System | Salesforce Platform (Spring '26) |
| API Surface | REST — Composite Resources |
| Current API Version | v66.0 (Spring '26) |
| Editions Covered | Enterprise, Unlimited, Developer, Performance |
| Deployment | Cloud |
| API Docs | REST API Composite Resources |
| Status | GA (all four resource types) |
API Surfaces & Capabilities
Salesforce provides four distinct composite resource types, each optimized for different integration patterns. The key differentiator is whether subrequests can reference each other's outputs and whether failures trigger full rollback.
| API Surface | Protocol | Best For | Max Subrequests | Reference IDs? | All-or-None? | Transactional? |
|---|---|---|---|---|---|---|
| Composite | HTTPS/JSON | Multi-step workflows needing cross-reference | 25 (max 5 queries/collections) | Yes | Optional (allOrNone param) | Yes, when allOrNone=true |
| Composite Graph | HTTPS/JSON | Large-scale related record creation; complex dependency graphs | 500 per graph | Yes (per graph only) | Implicit per graph | Yes, per graph |
| Composite Batch | HTTPS/JSON | Independent parallel operations (no dependencies) | 25 | No | No — each runs independently | No |
| sObject Collections | HTTPS/JSON | Bulk DML on single object type | 200 records | N/A | Optional (allOrNone param) | Yes, when allOrNone=true |
| sObject Tree | HTTPS/JSON | Parent-child record hierarchies | 200 records total | N/A (implicit parent-child) | Always all-or-nothing | Yes |
Rate Limits & Quotas
Per-Request Limits
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max subrequests (Composite) | 25 | Composite API | Max 5 can be query or sObject Collections operations |
| Max subrequests (Composite Graph) | 500 per graph | Composite Graph | Can have multiple graphs; 500 total across all graphs |
| Max subrequests (Composite Batch) | 25 | Composite Batch | Each subrequest executes independently |
| Max records (sObject Collections) | 200 | Collections create/update/delete/upsert | Per single Collections call |
| Max records (sObject Tree) | 200 total | sObject Tree | Max 5 levels deep; max 5 sObject types |
| Max query result size | 2,000 records | SOQL via Composite | Use queryMore for pagination |
| Max request body size | 50 MB | All REST API | Applies to entire composite payload |
Rolling / Daily Limits
| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| API calls | 100,000 base | 24h rolling | Enterprise: 100K + (users x 1,000); Unlimited: 5M; Developer: 15K |
| Concurrent long-running requests | 25 | Per org | Applies to requests >20s |
| Composite call consumption | 1 API call per composite request | Per request | All subrequests count as 1 call against daily limit |
Transaction / Governor Limits
Governor limits apply cumulatively across all subrequests within a single composite call. This is the most common source of composite API failures.
| Limit Type | Per-Transaction Value | Notes |
|---|---|---|
| SOQL queries | 100 (sync) / 200 (async) | Shared across all subrequests — triggers consume from same pool |
| DML statements | 150 | Each insert/update/delete counts as 1 |
| Records retrieved by SOQL | 50,000 | Total across all queries in the transaction |
| Records processed by DML | 10,000 | Total across all DML operations |
| CPU time | 10,000 ms (sync) / 60,000 ms (async) | Exceeded = entire transaction aborted |
| Heap size | 6 MB (sync) / 12 MB (async) | Monitor when handling large payloads |
| Callouts | 100 | If subrequests trigger Apex with callouts |
Authentication
All composite resources use the same authentication as standard Salesforce REST API. No special scopes or permissions required beyond standard API access.
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 JWT Bearer | Server-to-server, no user context | Session timeout (default 2h) | New JWT per request | Recommended for integrations |
| OAuth 2.0 Web Server | User-context operations | Access: 2h, Refresh: until revoked | Yes | Requires callback URL |
| OAuth 2.0 Client Credentials | Server-to-server (Spring '23+) | Access: 2h | No, request new token | Simpler than JWT |
Authentication Gotchas
- Connected App creation is disabled by default in Spring '26 (v66.0). Admins must explicitly enable it or migrate to External Client App feature. [src8]
- All subrequests execute in the context of the same user session — permission checks apply per-subrequest but the token is shared. [src1]
- Legacy hostname redirects were removed in Spring '26 — use My Domain URLs exclusively. [src8]
Constraints
- 25-subrequest ceiling on Composite API — cannot be increased. If you need more, use Composite Graph (500) or split into multiple requests.
- 5 query/collection cap within Composite — only 5 of the 25 subrequests can be queries or sObject Collections operations.
- Governor limits are cumulative — all subrequests share one governor context. 25 subrequests each doing 5 SOQL queries = 125 total, exceeding the 100-query sync limit.
- Composite Graph graphs are independent — reference IDs cannot cross graph boundaries. Each graph is its own transaction.
- sObject Collections limited to 200 records — for volumes above 200, use multiple Collections calls or switch to Bulk API 2.0.
- sObject Tree is always all-or-nothing — no partial success mode. If any record fails, the entire tree is rolled back.
- API versions 21.0-30.0 retired — minimum supported version is v31.0 as of June 2025.
Integration Pattern Decision Tree
START — Need to combine multiple Salesforce REST operations into one call
├── Do subrequests need to reference each other's output?
│ ├── YES — need reference IDs
│ │ ├── Need ≤25 subrequests? → Composite API (allOrNone=true for atomicity)
│ │ └── Need >25 subrequests? → Composite Graph API (up to 500 per graph)
│ └── NO — subrequests are independent
│ ├── All DML on same object type? → sObject Collections (200 max)
│ ├── Creating parent-child hierarchies? → sObject Tree (200 max)
│ └── Mixed independent operations? → Composite Batch (25 max)
├── Volume check
│ ├── ≤200 records of same type → sObject Collections
│ ├── ≤200 records with parent-child → sObject Tree
│ ├── ≤25 mixed operations with refs → Composite API
│ ├── ≤500 operations with refs → Composite Graph
│ └── >500 records → Bulk API 2.0
└── Error handling preference
├── All-or-nothing required → Composite (allOrNone=true), Graph, or Tree
├── Partial success OK → Batch or Collections (allOrNone=false)
└── Per-group atomicity → Composite Graph (each graph is atomic)
Quick Reference
| Operation | Resource | Method | Endpoint | Max Records | Ref IDs? | Atomicity |
|---|---|---|---|---|---|---|
| Multi-step workflow | Composite | POST | /services/data/v66.0/composite | 25 subrequests | Yes | Optional |
| Large dependency graph | Composite Graph | POST | /services/data/v66.0/composite/graph | 500/graph | Yes (per graph) | Per graph |
| Independent parallel ops | Composite Batch | POST | /services/data/v66.0/composite/batch | 25 subrequests | No | None |
| Bulk create (single type) | sObject Collections | POST | /services/data/v66.0/composite/sobjects | 200 records | N/A | Optional |
| Bulk update (single type) | sObject Collections | PATCH | /services/data/v66.0/composite/sobjects | 200 records | N/A | Optional |
| Bulk delete (single type) | sObject Collections | DELETE | /services/data/v66.0/composite/sobjects?ids=... | 200 records | N/A | Optional |
| Parent-child tree | sObject Tree | POST | /services/data/v66.0/composite/tree/{sObject} | 200 total | N/A | Always |
Step-by-Step Integration Guide
1. Authenticate and obtain access token
Use OAuth 2.0 JWT bearer flow for server-to-server integrations. [src1]
# Input: Connected App consumer key, private key, username
# Output: Access token and instance URL
curl -X POST https://login.salesforce.com/services/oauth2/token \
-d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
-d "assertion=$(generate_jwt_assertion)"
Verify: Response contains access_token and instance_url fields. HTTP 200 = success.
2. Execute a Composite request with reference IDs
Create an Account and linked Contact in a single atomic call. [src1, src4]
curl -X POST https://{instance_url}/services/data/v66.0/composite \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"allOrNone": true,
"collateSubrequests": true,
"compositeRequest": [
{
"method": "POST",
"url": "/services/data/v66.0/sobjects/Account",
"referenceId": "refAccount",
"body": {"Name": "Acme Corp", "Industry": "Technology"}
},
{
"method": "POST",
"url": "/services/data/v66.0/sobjects/Contact",
"referenceId": "refContact",
"body": {
"FirstName": "Jane", "LastName": "Doe",
"AccountId": "@{refAccount.id}"
}
}
]
}'
Verify: Response HTTP 200 with compositeResponse array. Each element has httpStatusCode: 200.
3. Execute a Composite Graph request
Use Composite Graph for >25 subrequests or multiple independent transactional groups. [src2, src3]
curl -X POST https://{instance_url}/services/data/v66.0/composite/graph \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"graphs": [
{
"graphId": "graph1",
"compositeRequest": [
{"method": "POST", "url": "/services/data/v66.0/sobjects/Account",
"referenceId": "ref1Acct", "body": {"Name": "Graph1 Corp"}},
{"method": "POST", "url": "/services/data/v66.0/sobjects/Contact",
"referenceId": "ref1Cont", "body": {"LastName": "Smith", "AccountId": "@{ref1Acct.id}"}}
]
},
{
"graphId": "graph2",
"compositeRequest": [
{"method": "POST", "url": "/services/data/v66.0/sobjects/Account",
"referenceId": "ref2Acct", "body": {"Name": "Graph2 Inc"}}
]
}
]
}'
Verify: Response graphs array. Each graph has isSuccessful: true/false. If graph1 fails, graph2 can still succeed.
4. Use sObject Collections for bulk DML
Batch create up to 200 records of the same sObject type. [src5]
curl -X POST https://{instance_url}/services/data/v66.0/composite/sobjects \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"allOrNone": false,
"records": [
{"attributes": {"type": "Contact"}, "FirstName": "Alice", "LastName": "Johnson"},
{"attributes": {"type": "Contact"}, "FirstName": "Bob", "LastName": "Williams"}
]
}'
Verify: Response is array of SaveResult: [{"id": "003xx...", "success": true, "errors": []}, ...]
Code Examples
Python: Composite request with reference IDs
# Input: Salesforce access token, instance URL
# Output: Created Account ID and linked Contact ID
import requests # requests>=2.31.0
def composite_create(instance_url, access_token):
"""Create Account + Contact atomically via Composite API."""
url = f"{instance_url}/services/data/v66.0/composite"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
payload = {
"allOrNone": True,
"collateSubrequests": True,
"compositeRequest": [
{
"method": "POST",
"url": "/services/data/v66.0/sobjects/Account",
"referenceId": "refAccount",
"body": {"Name": "Acme Corp"}
},
{
"method": "POST",
"url": "/services/data/v66.0/sobjects/Contact",
"referenceId": "refContact",
"body": {
"LastName": "Doe",
"AccountId": "@{refAccount.id}"
}
}
]
}
resp = requests.post(url, json=payload, headers=headers)
resp.raise_for_status()
results = resp.json()["compositeResponse"]
return {r["referenceId"]: r["body"]["id"] for r in results}
JavaScript/Node.js: sObject Collections bulk upsert
// Input: jsforce connection, array of records, external ID field
// Output: Array of SaveResult objects
const jsforce = require("jsforce"); // [email protected]
async function bulkCollectionsUpsert(conn, records, externalIdField) {
const CHUNK_SIZE = 200; // sObject Collections max
const results = [];
for (let i = 0; i < records.length; i += CHUNK_SIZE) {
const chunk = records.slice(i, i + CHUNK_SIZE);
const res = await conn.requestPost(
`/services/data/v66.0/composite/sobjects/${externalIdField}`,
{ allOrNone: false, records: chunk }
);
results.push(...res);
}
return results; // [{id, success, errors, created}, ...]
}
cURL: Composite Batch for independent queries
# Input: Access token, instance URL
# Output: Array of independent results
curl -X POST https://{instance_url}/services/data/v66.0/composite/batch \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"batchRequests": [
{"method": "GET", "url": "v66.0/sobjects/Account/describe"},
{"method": "GET", "url": "v66.0/query?q=SELECT+Id,Name+FROM+Account+LIMIT+5"}
]
}'
# Response: {"hasErrors": false, "results": [{statusCode: 200, result: {...}}, ...]}
Data Mapping
Reference ID Syntax
| Pattern | Purpose | Example | Notes |
|---|---|---|---|
@{referenceId.id} | Use created record's ID in later subrequest | "AccountId": "@{refAccount.id}" | Most common — link child to parent |
@{referenceId.field} | Access any field from response body | @{refQuery.records[0].Id} | Works with query results |
referenceId in URL | Dynamic URL construction | /sobjects/Account/@{refAccount.id} | For PATCH/DELETE after create |
Data Type Gotchas
- Reference IDs must contain only alphanumeric characters and underscores — no hyphens, dots, or special characters. [src1]
- The
@{refId.field}notation uses JavaScript-like dot syntax but does NOT support complex expressions or transformations. [src6] - When
collateSubrequests=true, the API may reorder independent subrequests to bulkify DML — do not rely on execution order for non-dependent subrequests. [src1] - sObject Collections
allOrNone=falsereturns HTTP 200 even if individual records failed — check each SaveResult'ssuccessfield. [src4]
Error Handling & Failure Points
Common Error Codes
| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 400 | Bad Request | Malformed JSON, invalid reference ID | Validate payload structure; check referenceId names |
| 401 | Unauthorized | Expired or invalid access token | Refresh token or re-authenticate |
| PROCESSING_HALTED | Subrequest skipped | allOrNone=true and earlier subrequest failed | Fix the failing subrequest |
| INVALID_FIELD | Field doesn't exist or isn't writable | Wrong API name or insufficient FLS | Check via /sobjects/{Object}/describe |
| UNABLE_TO_LOCK_ROW | Record locked | Concurrent updates to same record | Retry with random jitter (100-500ms) |
| LIMIT_EXCEEDED | Governor limit breached | Cumulative SOQL/DML/CPU exceeded | Reduce subrequest count or split requests |
| DUPLICATE_VALUE | Unique constraint violated | Duplicate external ID | Use upsert; implement idempotency |
Failure Points in Production
- Governor limit breach from trigger cascading: Each subrequest fires triggers consuming shared governor context. 25 inserts x 5 SOQL per trigger = 125 queries, exceeding 100-query limit. Fix:
audit triggers with Limits class; reduce SOQL per trigger. [src5] - allOrNone=true with validation rules: One record failing validation rolls back ALL records. Fix:
pre-validate data client-side before sending composite request. [src4] - Cross-graph reference attempt: Reference IDs scoped to graph — @{ref1.id} from graph1 not accessible in graph2. Fix:
design each graph as self-contained unit. [src2] - sObject Tree exceeding 5-level depth: Max 5 levels (root + 4 children). Fix:
flatten hierarchy or use Composite API with reference IDs. [src6] - collateSubrequests reordering: API may reorder independent subrequests. Fix:
use explicit reference IDs; set collateSubrequests=false if order matters. [src1] - Session timeout during large Composite Graph: 500-subrequest graphs can timeout. Fix:
ensure session timeout ≥30 min; use idempotent operations. [src2]
Anti-Patterns
Wrong: Making 25 individual API calls instead of 1 composite call
# BAD — 25 API calls consumed, 25 round trips
for record in records[:25]:
requests.post(f"{url}/sobjects/Contact", json=record, headers=headers)
Correct: Bundle into a single Composite or Collections call
# GOOD — 1 API call consumed, 1 round trip
payload = {
"allOrNone": False,
"records": [{"attributes": {"type": "Contact"}, **r} for r in records[:200]]
}
requests.post(f"{url}/composite/sobjects", json=payload, headers=headers)
Wrong: Using Composite Batch when you need atomicity
# BAD — Composite Batch has no allOrNone or reference IDs
# If subrequest 2 fails, subrequest 1 is already committed
payload = {"batchRequests": [
{"method": "POST", "url": "v66.0/sobjects/Account", "richInput": {"Name": "Acme"}},
{"method": "POST", "url": "v66.0/sobjects/Contact", "richInput": {"LastName": "Doe"}}
]}
Correct: Use Composite API with allOrNone for transactional workflows
# GOOD — allOrNone=true ensures both succeed or both roll back
payload = {
"allOrNone": True,
"compositeRequest": [
{"method": "POST", "url": "/services/data/v66.0/sobjects/Account",
"referenceId": "refAcct", "body": {"Name": "Acme"}},
{"method": "POST", "url": "/services/data/v66.0/sobjects/Contact",
"referenceId": "refCont", "body": {"LastName": "Doe", "AccountId": "@{refAcct.id}"}}
]
}
Wrong: Putting >5 queries inside Composite API
# BAD — Composite allows max 5 query/collection subrequests
payload = {"compositeRequest": [
{"method": "GET", "url": f"/services/data/v66.0/query?q=SELECT...", "referenceId": f"q{i}"}
for i in range(10) # 10 queries exceeds the 5-query cap
]}
Correct: Use Composite Batch for independent queries
# GOOD — Composite Batch has no query-count restriction within 25 subrequests
payload = {"batchRequests": [
{"method": "GET", "url": f"v66.0/query?q=SELECT...{table}"}
for table in tables[:25]
]}
Common Pitfalls
- Assuming Composite Graph is one transaction: The parent request is NOT transactional — each graph is independently transactional. Graph1 success + graph2 failure = graph1 committed. [src3]
- Ignoring the 5-query cap in Composite API: Only 5 of 25 subrequests can be queries or sObject Collections operations. [src1]
- Not handling partial success in Collections: HTTP 200 even when individual records fail. Check each SaveResult's
successfield. [src4] - Conflating Composite and Composite Batch: Batch has no reference IDs, no allOrNone, no transactional behavior. Choose Composite for dependencies, Batch for independent operations. [src1]
- Exceeding governor limits with trigger-heavy objects: 4 Account creates with 3 triggers x 10 SOQL each = 120 queries, exceeding 100-query limit. [src5]
- Using Composite for bulk data loads: Composite is for multi-step workflows, not data loading. For >500 records, use Bulk API 2.0. [src7]
Diagnostic Commands
# Check remaining API limits for this org
curl -H "Authorization: Bearer {token}" \
https://{instance_url}/services/data/v66.0/limits
# Verify composite resource availability
curl -H "Authorization: Bearer {token}" \
https://{instance_url}/services/data/v66.0/
# Describe an sObject to check field API names
curl -H "Authorization: Bearer {token}" \
https://{instance_url}/services/data/v66.0/sobjects/Account/describe
# Test a minimal composite batch request
curl -X POST -H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
https://{instance_url}/services/data/v66.0/composite/batch \
-d '{"batchRequests":[{"method":"GET","url":"v66.0/limits"}]}'
# Check available API versions
curl https://{instance_url}/services/data/
Version History & Compatibility
| API Version | Release | Status | Key Composite Features | Notes |
|---|---|---|---|---|
| v66.0 | Spring '26 (Feb 2026) | Current | No new composite features | Legacy hostname redirects removed |
| v63.0 | Spring '25 (Feb 2025) | Supported | None | — |
| v50.0 | Winter '21 (Oct 2020) | Supported | Composite Graph API introduced | 500 subrequests per graph |
| v43.0 | Summer '18 (Jun 2018) | Supported | sObject Collections introduced | 200-record DML |
| v38.0 | Winter '17 (Oct 2016) | Supported | Composite API introduced | 25 subrequests with reference IDs |
| v34.0 | Summer '15 (Jun 2015) | Supported | Composite Batch introduced | 25 independent subrequests |
| v30.0 and below | Pre-2014 | Retired (Jun 2025) | N/A | Minimum version: v31.0 |
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Creating related records atomically (parent + children) | Bulk data migration (>1,000 records) | Bulk API 2.0 |
| Need to reference output of one operation in a subsequent operation | Operations are completely independent | Composite Batch |
| Reducing API call consumption (25 ops = 1 API call) | Processing >500 related records | Bulk API 2.0 with external IDs |
| Building multi-step workflows (query, transform, upsert) | Simple single-object CRUD on one record | Standard REST API |
| Creating up to 200 records of same type efficiently | Need real-time event notifications | Platform Events / CDC |
Important Caveats
- Composite API subrequest limits (25, 500) are hard platform limits — cannot be increased via support tickets or add-on licenses.
- Governor limits are the real bottleneck — trigger-heavy orgs may only be able to use 5-10 subrequests before hitting SOQL or DML limits.
- Sandbox and Developer editions have lower API daily call limits (15K for Developer vs 100K+ for Enterprise).
- The
collateSubrequestsparameter only affects DML ordering optimization — it does not change allOrNone semantics. - Composite Graph requires API v50.0+. Integrations pinned to older versions cannot use Composite Graph.
- All composite resources respect field-level security — if the integration user cannot see a field, it is silently omitted from responses.
- Rate limits change with each Salesforce release — always verify against the current Salesforce Developer Limits Quick Reference.