Salesforce REST API Capabilities, Rate Limits & Governor Limits by Edition
What are the Salesforce REST API capabilities, rate limits, and governor limits by edition?
TL;DR
- Bottom line: Salesforce REST API (v66.0, Spring '26) handles real-time CRUD for individual records and small batches (<2,000 records); switch to Bulk API 2.0 for anything larger. Daily API call limits vary dramatically by edition.
- Key limit: 100,000 base API calls/24h + per-user additions (Enterprise: +1,000/user; Unlimited: +5,000/user). This pool is shared across REST, SOAP, Bulk, and Connect APIs. [src1]
- Watch out for: Governor limits are per-transaction, not per-API-call — a single API request can trigger Apex code that cascades through 100 SOQL queries, 150 DML statements, and 10,000 DML rows before hitting the wall. [src3]
- Best for: Real-time CRM operations under 2,000 records per request — account sync, opportunity management, custom object CRUD, and composite multi-object operations.
- Authentication: OAuth 2.0 JWT bearer flow for server-to-server (recommended); web server flow for user-context; username-password flow deprecated for production. [src2]
System Profile
This card covers the Salesforce REST API across all major commercial editions (Professional, Enterprise, Unlimited, Performance) and the free Developer Edition. Rate limits and governor limits differ significantly by edition — always check which edition your org runs before designing an integration. This card does not cover Salesforce Marketing Cloud (separate API) or MuleSoft Anypoint (separate product).
| Property | Value |
|---|---|
| Vendor | Salesforce |
| System | Salesforce CRM / Platform |
| API Surface | REST (primary), plus SOAP, Bulk 2.0, Streaming, Composite, Connect |
| Current API Version | v66.0 (Spring '26, released February 2026) |
| Editions Covered | Professional (with API add-on), Enterprise, Unlimited, Performance, Developer |
| Deployment | Cloud (multi-tenant SaaS) |
| API Docs | REST API Developer Guide |
| Status | GA (General Availability) |
API Surfaces & Capabilities
Salesforce exposes multiple API surfaces for different use cases. Choose based on data volume, latency requirements, and direction. [src1, src2]
| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| REST API | HTTPS/JSON | Individual record CRUD, queries <2K records | 2,000 (SOQL query), 200 (composite) | Shared daily pool | Yes | No |
| Bulk API 2.0 | HTTPS/CSV | ETL, data migration, >2K records | 150 MB per job file | 100M records/24h; 25 concurrent jobs | No | Yes |
| SOAP API | HTTPS/XML | Metadata operations, legacy integrations | 2,000 records | Shared daily pool with REST | Yes | No |
| Composite API | HTTPS/JSON | Multi-object operations in one call | 25 subrequests | Shared daily pool (counts as 1 API call) | Yes | No |
| Streaming API | Bayeux/CometD | Real-time push notifications | N/A | 50K–1M events/day (edition-dependent) | Yes | N/A |
| Platform Events | HTTPS/JSON | Event-driven architecture, pub/sub | N/A | 250K publishes/hour; 50K deliveries/day (standard) | Yes | N/A |
| Change Data Capture | Bayeux/CometD | Track record changes in real-time | N/A | Shared with Platform Events allocation | Yes | N/A |
| Connect API | HTTPS/JSON | Chatter, files, communities, UI-specific | Varies | Separate per-user-per-app-per-hour limit | Yes | No |
Rate Limits & Quotas
Per-Request Limits
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max records per SOQL query page | 2,000 | REST API | Use queryMore or nextRecordsUrl for pagination |
| Max SOQL query rows per transaction | 50,000 | All APIs triggering Apex | Aggregate across all queries in one transaction |
| Max request body size | 50 MB | REST API | For single record operations |
| Max composite subrequests | 25 | Composite API | All-or-nothing by default; allOrNone flag controls rollback |
| Max SObject Collection records | 200 | SObject Collections | For create/update/delete in a single request |
| Max Bulk API 2.0 job file size | 150 MB | Bulk API 2.0 | Base64 encoding adds ~50% — limit to 100 MB pre-encoding |
| API call timeout | 600,000 ms (10 min) | REST and SOAP API | Except query calls which have no timeout |
| Max fields in SOQL SELECT | 200 | REST API queries | Use multiple queries for wide objects |
Rolling / Daily Limits
| Limit Type | Base Value | Window | Edition Differences |
|---|---|---|---|
| Total API calls | 100,000 | 24h rolling | Professional: 100K + 1,000/user; Enterprise: 100K + 1,000/user; Unlimited/Performance: 100K + 5,000/user; Developer: 15,000 total |
| Bulk API 2.0 records processed | 100,000,000 | 24h | Shared across all editions |
| Bulk API batches | 15,000 | 24h | Shared between Bulk API 1.0 and 2.0 |
| Concurrent long-running API requests | 25 | Per org | Developer: 5 concurrent; requests >20s count as long-running |
| Streaming API events | 50K–1M | 24h | Enterprise: 200K; Unlimited/Performance: 1M |
| Platform Events + CDC deliveries | 50,000 | 24h (standard) | Add-on license: 100K extra, enforced monthly (4.5M/month) |
| Platform Events publishes | 250,000 | Per hour | Rarely a bottleneck |
| Sandbox API calls | 5,000,000 | 24h | Fixed, independent of edition |
Transaction / Governor Limits
These limits apply per Apex transaction — a single API request can trigger Apex code (triggers, flows, process builders) that must stay within these boundaries. This is the #1 area where agents hallucinate. [src3]
| Limit Type | Synchronous Value | Asynchronous Value | Notes |
|---|---|---|---|
| SOQL queries | 100 | 200 | Includes queries from triggers — cascading triggers consume from same pool |
| Total SOQL query rows returned | 50,000 | 50,000 | Aggregate across all queries in the transaction |
| DML statements | 150 | 150 | Each insert/update/delete/upsert counts as 1 |
| DML rows | 10,000 | 10,000 | Total records across all DML operations |
| CPU time | 10,000 ms | 60,000 ms | Exceeded = transaction abort with System.LimitException |
| Heap size | 6 MB | 12 MB | Watch for large query results stored in memory |
| Callouts (HTTP requests) | 100 | 100 | External service calls within a transaction |
| Callout timeout | 120,000 ms total | 120,000 ms total | Per-callout max: 120s; total across all callouts: 120s |
| Future method invocations | 50 | 0 (not allowed) | Cannot call @future from @future |
| Queueable jobs queued | 50 | 50 | Per transaction |
| Email invocations | 10 | 10 | SingleEmailMessage sends per transaction |
| SOSL searches | 20 | 20 | Per transaction |
Authentication
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 JWT Bearer | Server-to-server, no user context | Session timeout (default 2h) | No — issue new JWT per request | Recommended for integrations; requires connected app + X.509 certificate |
| OAuth 2.0 Web Server | User-context operations, interactive apps | Access: 2h; Refresh: until revoked or 90 days of non-use | Yes | Requires callback URL |
| OAuth 2.0 Client Credentials | Server-to-server (Spring '23+), simpler than JWT | Session timeout | No refresh token | No user context |
| Username-Password | Testing only, legacy | Session timeout | No | Do NOT use in production — no MFA support |
Authentication Gotchas
- JWT bearer flow requires a connected app with digital certificate — self-signed works for dev, CA-signed for production. [src2]
- Access tokens from JWT flow are org-scoped — all API calls execute with the integration user's permissions and profile. [src2]
- Session timeout is admin-configurable (15 min to 24h) — never hardcode 2 hours. [src2]
- OAuth refresh tokens expire after 90 days of non-use — use JWT bearer flow for unattended integrations. [src4]
- Spring '26: Connected app creation is now disabled by default. Use External Client App feature instead. [src6]
Constraints
- Professional Edition requires API add-on: API access is not included by default. Do not recommend API integrations without confirming the add-on is active.
- Daily API pool is shared: REST, SOAP, Bulk, and Connect API calls all draw from the same daily pool. A heavy Bulk API job can starve real-time REST integrations.
- Governor limits cascade through triggers: A single REST API upsert can fire triggers that share the same 100-SOQL, 150-DML, 10,000-row transaction budget.
- Composite API subrequests count as 1 API call: 25 subrequests consume only 1 from daily quota, but still subject to governor limits per transaction.
- No API for formula field writes: Formula fields, rollup summary fields, and system fields cannot be written via API.
- Sandbox limits differ from production: Sandbox orgs have a fixed 5M API calls/24h regardless of edition.
- API version minimum is v31.0: Versions 21.0-30.0 were retired June 2025. Integrations pinned below v31.0 will fail.
- Platform Events require capacity planning: Standard allocation of 50,000 daily event deliveries is shared between Platform Events and CDC.
Integration Pattern Decision Tree
START — User needs to integrate with Salesforce REST API
|-- What's the integration pattern?
| |-- Real-time (individual records, <1s latency)
| | |-- Data volume < 200 records/operation?
| | | |-- YES --> REST API: SObject Collections (multi-record) or Composite (multi-object)
| | | +-- NO --> REST API with chunking (200-record batches) + async processing
| | +-- Need notifications/webhooks?
| | |-- YES --> Platform Events or Change Data Capture
| | +-- NO --> REST API polling with SystemModstamp filter
| |-- Batch/Bulk (scheduled, high volume)
| | |-- Data volume < 2,000 records?
| | | |-- YES --> REST API SObject Collections (simpler, no batch overhead)
| | | +-- NO --> Bulk API 2.0 (single job <100K; chunking for >100K)
| | +-- Need real-time progress tracking?
| | |-- YES --> Bulk API 2.0 (poll job status endpoint)
| | +-- NO --> Bulk API 2.0 with completion notification
| |-- Event-driven (CDC, real-time change tracking)
| | |-- Need guaranteed delivery?
| | | |-- YES --> Platform Events with replay (72h retention)
| | | +-- NO --> Streaming API / PushTopics
| | +-- Need cross-object change tracking?
| | |-- YES --> Change Data Capture
| | +-- NO --> Object-specific triggers + Platform Events
| +-- File-based (CSV import)
| +-- Use Bulk API 2.0 with CSV upload
|-- Which direction?
| |-- Inbound (writing to SF) --> check daily API limits + governor limits for triggers
| |-- Outbound (reading from SF) --> check query row limits (50K/transaction)
| +-- Bidirectional --> design conflict resolution FIRST (external ID + last-modified wins)
+-- Error tolerance?
|-- Zero-loss --> implement idempotency (external ID upsert) + dead letter queue
+-- Best-effort --> fire-and-forget with exponential backoff retry
Quick Reference
Key REST API Endpoints
| Operation | Method | Endpoint | Payload | Notes |
|---|---|---|---|---|
| Create record | POST | /services/data/v66.0/sobjects/{Object} | JSON | Returns record ID on 201 success |
| Read record | GET | /services/data/v66.0/sobjects/{Object}/{id} | N/A | Specify fields with ?fields= |
| Update record | PATCH | /services/data/v66.0/sobjects/{Object}/{id} | JSON | Partial update — only changed fields |
| Delete record | DELETE | /services/data/v66.0/sobjects/{Object}/{id} | N/A | Returns 204 No Content |
| Upsert by external ID | PATCH | /services/data/v66.0/sobjects/{Object}/{ExtIdField}/{ExtIdValue} | JSON | Idempotent create/update |
| Query (SOQL) | GET | /services/data/v66.0/query?q={SOQL} | N/A | Max 2,000 records per page |
| Query more | GET | /services/data/v66.0/query/{queryLocator} | N/A | Continue paginating |
| Composite | POST | /services/data/v66.0/composite | JSON | Up to 25 subrequests |
| SObject Collections | POST | /services/data/v66.0/composite/sobjects | JSON array | Up to 200 records per call |
| Describe object | GET | /services/data/v66.0/sobjects/{Object}/describe | N/A | Full object metadata |
| Check limits | GET | /services/data/v66.0/limits | N/A | Remaining allocations for all limits |
| Search (SOSL) | GET | /services/data/v66.0/search?q={SOSL} | N/A | Full-text search across objects |
Step-by-Step Integration Guide
1. Authenticate and obtain access token
Use the JWT bearer flow for server-to-server integrations. Create a connected app in Salesforce Setup, upload your X.509 certificate, and configure pre-authorized profiles. [src2]
# Generate JWT and exchange for access token
curl -X POST https://login.salesforce.com/services/oauth2/token \
-d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
-d "assertion=<your_signed_jwt>"
Verify: Response contains access_token and instance_url
2. Query records with SOQL
Use the query endpoint for structured reads. Always paginate — default page size is 2,000 records. [src2]
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
"$INSTANCE_URL/services/data/v66.0/query?q=SELECT+Id,Name,Industry+FROM+Account+WHERE+LastModifiedDate+>+2026-01-01T00:00:00Z+LIMIT+100"
Verify: Response includes totalSize, done, and records array
3. Create or update records with upsert
Use external ID upsert for idempotent writes — prevents duplicates on retry. [src2]
curl -X PATCH \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"$INSTANCE_URL/services/data/v66.0/sobjects/Account/External_ID__c/EXT-001" \
-d '{"Name": "Acme Corp", "Industry": "Technology"}'
Verify: 201 (created) or 204 (updated)
4. Handle pagination for large result sets
When done is false, use nextRecordsUrl to fetch the next page. [src2]
import requests
def query_all(instance_url, access_token, soql):
headers = {"Authorization": f"Bearer {access_token}"}
url = f"{instance_url}/services/data/v66.0/query"
params = {"q": soql}
all_records = []
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
data = response.json()
all_records.extend(data["records"])
while not data["done"]:
next_url = f"{instance_url}{data['nextRecordsUrl']}"
response = requests.get(next_url, headers=headers)
response.raise_for_status()
data = response.json()
all_records.extend(data["records"])
return all_records
Verify: len(all_records) == totalSize
5. Implement error handling and retry logic
Handle 429 (rate limit), 503 (service unavailable), and expired tokens with exponential backoff. [src4]
import time, requests
def sf_api_call(method, url, headers, json=None, max_retries=5):
for attempt in range(max_retries):
response = requests.request(method, url, headers=headers, json=json)
if response.status_code == 429:
time.sleep(min(2 ** attempt * 2, 60))
continue
elif response.status_code == 503:
time.sleep(min(2 ** attempt * 5, 120))
continue
elif response.status_code == 401:
headers["Authorization"] = f"Bearer {refresh_access_token()}"
continue
return response
raise Exception(f"Max retries exceeded for {url}")
Verify: Function returns valid response and retries on 429/503
Code Examples
Python: JWT Auth + SObject Collections Upsert
# Input: Salesforce credentials (JWT), list of account records
# Output: Upsert results with success/failure counts
import requests, jwt, time
from datetime import datetime, timedelta, timezone
from cryptography.hazmat.primitives import serialization
class SalesforceClient:
def __init__(self, consumer_key, username, private_key_path,
login_url="https://login.salesforce.com"):
self.consumer_key = consumer_key
self.username = username
self.login_url = login_url
with open(private_key_path, "rb") as f:
self.private_key = serialization.load_pem_private_key(f.read(), password=None)
self.access_token = self.instance_url = None
def authenticate(self):
now = datetime.now(timezone.utc)
payload = {"iss": self.consumer_key, "sub": self.username,
"aud": self.login_url,
"exp": int((now + timedelta(minutes=5)).timestamp())}
token = jwt.encode(payload, self.private_key, algorithm="RS256")
resp = requests.post(f"{self.login_url}/services/oauth2/token",
data={"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": token})
resp.raise_for_status()
data = resp.json()
self.access_token = data["access_token"]
self.instance_url = data["instance_url"]
def upsert_collection(self, sobject, ext_id_field, records):
url = (f"{self.instance_url}/services/data/v66.0"
f"/composite/sobjects/{sobject}/{ext_id_field}")
resp = requests.patch(url, headers=self._headers(),
json={"allOrNone": False, "records": records})
resp.raise_for_status()
return resp.json()
def _headers(self):
return {"Authorization": f"Bearer {self.access_token}",
"Content-Type": "application/json"}
JavaScript/Node.js: Composite API Multi-Object Operation
// Input: Salesforce access token, instance URL
// Output: Composite response with all subrequest results
async function compositeOperation(instanceUrl, accessToken) {
const url = `${instanceUrl}/services/data/v66.0/composite`;
const body = {
allOrNone: true,
compositeRequest: [
{ method: "POST", url: "/services/data/v66.0/sobjects/Account",
referenceId: "newAccount",
body: { Name: "Acme Corp", Industry: "Technology" } },
{ method: "POST", url: "/services/data/v66.0/sobjects/Contact",
referenceId: "newContact",
body: { FirstName: "Jane", LastName: "Doe",
AccountId: "@{newAccount.id}" } }
]
};
const response = await fetch(url, {
method: "POST",
headers: { "Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json" },
body: JSON.stringify(body)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
cURL: Check API Limits
# Input: Valid access token, instance URL
# Output: Remaining API quota
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
"$INSTANCE_URL/services/data/v66.0/limits" | \
python3 -c "import sys,json; d=json.load(sys.stdin); \
print(f'Daily API: {d[\"DailyApiRequests\"][\"Remaining\"]}/{d[\"DailyApiRequests\"][\"Max\"]}')"
# Expected: Daily API: 95432/100000
Data Mapping
Field Mapping Reference
| API Field | Type | API Writable? | Max Length | Gotcha |
|---|---|---|---|---|
Id | ID (18-char) | No (auto-generated) | 18 | Always use 18-char in integrations |
Name | String | Yes | 255 (Account) | Auto-generated for some objects |
CreatedDate | DateTime | No (system field) | N/A | Cannot be set via API; query only |
SystemModstamp | DateTime | No (system field) | N/A | More reliable than LastModifiedDate for sync |
External_ID__c | String/Number | Yes | Custom | Use for idempotent upserts; must be unique + external ID flagged |
| Formula fields | Varies | No (calculated) | N/A | Cannot write — agents must not suggest writing |
| Multi-select picklist | String | Yes | 4,099 | Values separated by semicolons (;), not commas |
Data Type Gotchas
- DateTime is always UTC in the API (
2026-03-02T14:30:00.000+0000); displays in user timezone in UI. [src2] - Multi-select picklist fields serialize as semicolon-delimited in API (
"Value1;Value2") but comma-delimited in UI. [src2] - Boolean fields accept
true/false(lowercase JSON). Sending"True"(string) or1(integer) fails. [src2] - Lookup fields require 18-character Salesforce ID. 15-char IDs are unreliable in API calls. [src2]
Error Handling & Failure Points
Common Error Codes
| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 401 | INVALID_SESSION_ID | Access token expired or revoked | Re-authenticate; generate new JWT token |
| 403 | REQUEST_LIMIT_EXCEEDED | Daily API call limit exceeded | Wait for 24h window reset; check /limits; purchase add-on calls |
| 404 | NOT_FOUND | Record ID doesn't exist or was deleted | Verify record ID; check recycle bin |
| 429 | Too Many Requests | Concurrent request limit exceeded | Exponential backoff: wait 2^n seconds, max 5 retries |
| 503 | Service Unavailable | Salesforce maintenance or rate limit | Retry with backoff; check trust.salesforce.com |
| UNABLE_TO_LOCK_ROW | Record locked | Concurrent updates to same record | Retry with random jitter; implement locking strategy |
| INVALID_FIELD | Field inaccessible | Wrong API version or missing FLS | Check field-level security; verify API version |
| DUPLICATE_VALUE | Unique constraint violation | External ID already exists | Use upsert instead of insert |
Failure Points in Production
- Trigger recursion causes governor limit breach: REST API upsert fires cascading triggers consuming shared SOQL/DML pool. Fix:
Implement static boolean flags to prevent trigger re-entry. [src3] - Bulk API jobs fail on BOM characters: UTF-8 BOM causes first column header rejection. Fix:
Strip BOM from CSV; use encoding='utf-8-sig' in Python. [src5] - OAuth refresh token expires after 90 days: Infrequent integrations lose their token. Fix:
Use JWT bearer flow or implement weekly token refresh cron. [src4] - FLS blocks API access silently: Fields return
nullinstead of error when integration user lacks FLS. Fix:Test with integration user profile; use describe calls to verify. [src2] - Sandbox refresh resets connected apps: Connected apps may need re-authorization after refresh. Fix:
Automate via Metadata API; document post-refresh runbook. [src4] - Sharing rules restrict record visibility: Integration user may not see all records. Fix:
Grant "View All" or "Modify All" on required objects. [src2]
Anti-Patterns
Wrong: Querying all records to detect changes
# BAD -- Fetches entire object, wastes API calls
all_accounts = sf.query("SELECT Id, Name FROM Account")
# Then compare locally... O(n) on every sync
Correct: Use SystemModstamp filter for incremental sync
# GOOD -- Only fetches records modified since last sync
changed = sf.query(
f"SELECT Id, Name FROM Account "
f"WHERE SystemModstamp > {last_sync}"
)
Wrong: Individual API calls in a loop
# BAD -- 1,000 API calls for 1,000 records
for record in records:
sf.Account.create(record) # 1 API call each
Correct: Use SObject Collections for batch operations
# GOOD -- 5 API calls for 1,000 records (200 per call)
for chunk in chunks(records, 200):
sf.composite.sobjects.create("Account", chunk)
Wrong: Synchronous callouts inside triggers
// BAD -- Callout in a loop hits 100-callout limit
trigger AccountSync on Account (after insert) {
for (Account a : Trigger.new) {
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint('https://external.api/sync');
h.send(req);
}
}
Correct: Collect records, process asynchronously
// GOOD -- Collect IDs, process in @future or Queueable
trigger AccountSync on Account (after insert) {
Set<Id> accountIds = new Set<Id>();
for (Account a : Trigger.new) {
accountIds.add(a.Id);
}
AccountSyncService.syncAsync(accountIds);
}
Common Pitfalls
- Sandbox != Production API limits: Sandbox has 5M/day but production may have only 100K. Fix:
Use full-copy sandbox with realistic data volumes. [src4] - Not pinning API version: Defaults to latest; breaks when release changes behavior. Fix:
Pin version in base URL (e.g., /services/data/v66.0/). [src2] - Ignoring field-level security: Works with admin profile, fails with integration user. Fix:
Create dedicated permission set; use describe calls. [src2] - Not handling partial success: SObject Collections with
allOrNone: falsereturns mixed results. Fix:Check each result's 'success' flag individually. [src2] - Hardcoding org URLs: Legacy hostname redirections removed in Spring '26. Fix:
Always use instance_url from OAuth response. [src6] - Exceeding DML rows in batch triggers: Bulk batch of 10K records + cascading updates exceeds 10K DML rows. Fix:
Reduce batch size to 200 when complex triggers exist. [src3]
Diagnostic Commands
# Check API usage / remaining limits
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
"$INSTANCE_URL/services/data/v66.0/limits" | \
python3 -c "
import sys, json
limits = json.load(sys.stdin)
for key in ['DailyApiRequests','ConcurrentAsyncGetReportInstances','DailyBulkV2QueryJobs']:
l = limits.get(key, {})
print(f'{key}: {l.get(\"Remaining\",\"?\")}/{l.get(\"Max\",\"?\")}')
"
# Test authentication (verify token is valid)
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
"$INSTANCE_URL/services/data/v66.0/"
# Expected: 200
# Verify object/field accessibility
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
"$INSTANCE_URL/services/data/v66.0/sobjects/Account/describe" | \
python3 -c "
import sys, json
obj = json.load(sys.stdin)
print(f'Object: {obj[\"name\"]}, Queryable: {obj[\"queryable\"]}')
for f in obj['fields'][:10]:
print(f' {f[\"name\"]}: createable={f[\"createable\"]}')
"
# Monitor Bulk API 2.0 job status
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
"$INSTANCE_URL/services/data/v66.0/jobs/ingest/$JOB_ID"
Version History & Compatibility
| API Version | Release | Status | Breaking Changes | Migration Notes |
|---|---|---|---|---|
| v66.0 | Spring '26 (Feb 2026) | Current | Legacy hostname redirections removed; connected app creation disabled by default | Update URLs to My Domain; use External Client App |
| v65.0 | Winter '26 (Oct 2025) | Supported | None significant | — |
| v64.0 | Summer '25 (Jun 2025) | Supported | API versions 21.0-30.0 retired | Minimum version is now v31.0 |
| v63.0 | Spring '25 (Feb 2025) | Supported | — | — |
| v62.0 | Winter '25 (Oct 2024) | Supported | — | — |
| v58.0 | Spring '24 (Feb 2024) | Supported | — | Min version for newer composite features |
Deprecation Policy: Salesforce supports API versions for a minimum of 3 years (~9 releases). Versions retired in groups: 7.0-20.0 in 2022, 21.0-30.0 in June 2025. Pin your version and upgrade deliberately. [API End-of-Life Policy]
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Real-time individual record CRUD (<200 records/operation) | Data migration or ETL >2,000 records | Bulk API 2.0 |
| Multi-object operations needing atomicity (Composite API) | Scheduled batch processing of >10K records | Bulk API 2.0 with scheduled jobs |
| Interactive user-facing apps needing immediate response | Real-time event notification without polling | Platform Events or CDC |
| Exploring/testing API with simple cURL commands | Metadata deployment (field creation, layouts) | Metadata API or Tooling API |
| Small to medium integrations with <50K API calls/day | High-frequency polling (>1 call/second sustained) | Streaming API or CDC |
Important Caveats
- Rate limits vary dramatically by Salesforce edition. Developer Edition has only 15,000 API calls/24h and 5 concurrent requests — never use for load testing or production.
- Sandbox API limits (5M/day) differ from production and reset independently. Integration passing in sandbox may hit limits in production.
- The daily API call limit is a soft limit — Salesforce allows exceeding temporarily but will enforce hard block with
REQUEST_LIMIT_EXCEEDEDif sustained. - Platform Events and CDC share a combined 50K/day delivery allocation in standard tiers. High-volume event architectures require the add-on license.
- Governor limits (SOQL, DML, CPU) are per-transaction and apply regardless of API used — even Bulk API triggers Apex code subject to governor limits.
- All numbers in this card reflect Salesforce docs as of March 2026 (API v66.0, Spring '26). Always verify against
/limitsendpoint and current release notes. - EU instances may have different data residency constraints. Cross-region API calls are allowed but may have additional latency.