Salesforce REST API Capabilities, Rate Limits & Governor Limits by Edition

Type: ERP Integration System: Salesforce (API v66.0, Spring '26) Confidence: 0.92 Sources: 7 Verified: 2026-03-02 Freshness: evolving

TL;DR

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).

PropertyValue
VendorSalesforce
SystemSalesforce CRM / Platform
API SurfaceREST (primary), plus SOAP, Bulk 2.0, Streaming, Composite, Connect
Current API Versionv66.0 (Spring '26, released February 2026)
Editions CoveredProfessional (with API add-on), Enterprise, Unlimited, Performance, Developer
DeploymentCloud (multi-tenant SaaS)
API DocsREST API Developer Guide
StatusGA (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 SurfaceProtocolBest ForMax Records/RequestRate LimitReal-time?Bulk?
REST APIHTTPS/JSONIndividual record CRUD, queries <2K records2,000 (SOQL query), 200 (composite)Shared daily poolYesNo
Bulk API 2.0HTTPS/CSVETL, data migration, >2K records150 MB per job file100M records/24h; 25 concurrent jobsNoYes
SOAP APIHTTPS/XMLMetadata operations, legacy integrations2,000 recordsShared daily pool with RESTYesNo
Composite APIHTTPS/JSONMulti-object operations in one call25 subrequestsShared daily pool (counts as 1 API call)YesNo
Streaming APIBayeux/CometDReal-time push notificationsN/A50K–1M events/day (edition-dependent)YesN/A
Platform EventsHTTPS/JSONEvent-driven architecture, pub/subN/A250K publishes/hour; 50K deliveries/day (standard)YesN/A
Change Data CaptureBayeux/CometDTrack record changes in real-timeN/AShared with Platform Events allocationYesN/A
Connect APIHTTPS/JSONChatter, files, communities, UI-specificVariesSeparate per-user-per-app-per-hour limitYesNo

Rate Limits & Quotas

Per-Request Limits

Limit TypeValueApplies ToNotes
Max records per SOQL query page2,000REST APIUse queryMore or nextRecordsUrl for pagination
Max SOQL query rows per transaction50,000All APIs triggering ApexAggregate across all queries in one transaction
Max request body size50 MBREST APIFor single record operations
Max composite subrequests25Composite APIAll-or-nothing by default; allOrNone flag controls rollback
Max SObject Collection records200SObject CollectionsFor create/update/delete in a single request
Max Bulk API 2.0 job file size150 MBBulk API 2.0Base64 encoding adds ~50% — limit to 100 MB pre-encoding
API call timeout600,000 ms (10 min)REST and SOAP APIExcept query calls which have no timeout
Max fields in SOQL SELECT200REST API queriesUse multiple queries for wide objects

Rolling / Daily Limits

Limit TypeBase ValueWindowEdition Differences
Total API calls100,00024h rollingProfessional: 100K + 1,000/user; Enterprise: 100K + 1,000/user; Unlimited/Performance: 100K + 5,000/user; Developer: 15,000 total
Bulk API 2.0 records processed100,000,00024hShared across all editions
Bulk API batches15,00024hShared between Bulk API 1.0 and 2.0
Concurrent long-running API requests25Per orgDeveloper: 5 concurrent; requests >20s count as long-running
Streaming API events50K–1M24hEnterprise: 200K; Unlimited/Performance: 1M
Platform Events + CDC deliveries50,00024h (standard)Add-on license: 100K extra, enforced monthly (4.5M/month)
Platform Events publishes250,000Per hourRarely a bottleneck
Sandbox API calls5,000,00024hFixed, 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 TypeSynchronous ValueAsynchronous ValueNotes
SOQL queries100200Includes queries from triggers — cascading triggers consume from same pool
Total SOQL query rows returned50,00050,000Aggregate across all queries in the transaction
DML statements150150Each insert/update/delete/upsert counts as 1
DML rows10,00010,000Total records across all DML operations
CPU time10,000 ms60,000 msExceeded = transaction abort with System.LimitException
Heap size6 MB12 MBWatch for large query results stored in memory
Callouts (HTTP requests)100100External service calls within a transaction
Callout timeout120,000 ms total120,000 ms totalPer-callout max: 120s; total across all callouts: 120s
Future method invocations500 (not allowed)Cannot call @future from @future
Queueable jobs queued5050Per transaction
Email invocations1010SingleEmailMessage sends per transaction
SOSL searches2020Per transaction

Authentication

FlowUse WhenToken LifetimeRefresh?Notes
OAuth 2.0 JWT BearerServer-to-server, no user contextSession timeout (default 2h)No — issue new JWT per requestRecommended for integrations; requires connected app + X.509 certificate
OAuth 2.0 Web ServerUser-context operations, interactive appsAccess: 2h; Refresh: until revoked or 90 days of non-useYesRequires callback URL
OAuth 2.0 Client CredentialsServer-to-server (Spring '23+), simpler than JWTSession timeoutNo refresh tokenNo user context
Username-PasswordTesting only, legacySession timeoutNoDo NOT use in production — no MFA support

Authentication Gotchas

Constraints

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

OperationMethodEndpointPayloadNotes
Create recordPOST/services/data/v66.0/sobjects/{Object}JSONReturns record ID on 201 success
Read recordGET/services/data/v66.0/sobjects/{Object}/{id}N/ASpecify fields with ?fields=
Update recordPATCH/services/data/v66.0/sobjects/{Object}/{id}JSONPartial update — only changed fields
Delete recordDELETE/services/data/v66.0/sobjects/{Object}/{id}N/AReturns 204 No Content
Upsert by external IDPATCH/services/data/v66.0/sobjects/{Object}/{ExtIdField}/{ExtIdValue}JSONIdempotent create/update
Query (SOQL)GET/services/data/v66.0/query?q={SOQL}N/AMax 2,000 records per page
Query moreGET/services/data/v66.0/query/{queryLocator}N/AContinue paginating
CompositePOST/services/data/v66.0/compositeJSONUp to 25 subrequests
SObject CollectionsPOST/services/data/v66.0/composite/sobjectsJSON arrayUp to 200 records per call
Describe objectGET/services/data/v66.0/sobjects/{Object}/describeN/AFull object metadata
Check limitsGET/services/data/v66.0/limitsN/ARemaining allocations for all limits
Search (SOSL)GET/services/data/v66.0/search?q={SOSL}N/AFull-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 FieldTypeAPI Writable?Max LengthGotcha
IdID (18-char)No (auto-generated)18Always use 18-char in integrations
NameStringYes255 (Account)Auto-generated for some objects
CreatedDateDateTimeNo (system field)N/ACannot be set via API; query only
SystemModstampDateTimeNo (system field)N/AMore reliable than LastModifiedDate for sync
External_ID__cString/NumberYesCustomUse for idempotent upserts; must be unique + external ID flagged
Formula fieldsVariesNo (calculated)N/ACannot write — agents must not suggest writing
Multi-select picklistStringYes4,099Values separated by semicolons (;), not commas

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodeMeaningCauseResolution
401INVALID_SESSION_IDAccess token expired or revokedRe-authenticate; generate new JWT token
403REQUEST_LIMIT_EXCEEDEDDaily API call limit exceededWait for 24h window reset; check /limits; purchase add-on calls
404NOT_FOUNDRecord ID doesn't exist or was deletedVerify record ID; check recycle bin
429Too Many RequestsConcurrent request limit exceededExponential backoff: wait 2^n seconds, max 5 retries
503Service UnavailableSalesforce maintenance or rate limitRetry with backoff; check trust.salesforce.com
UNABLE_TO_LOCK_ROWRecord lockedConcurrent updates to same recordRetry with random jitter; implement locking strategy
INVALID_FIELDField inaccessibleWrong API version or missing FLSCheck field-level security; verify API version
DUPLICATE_VALUEUnique constraint violationExternal ID already existsUse upsert instead of insert

Failure Points in Production

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

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 VersionReleaseStatusBreaking ChangesMigration Notes
v66.0Spring '26 (Feb 2026)CurrentLegacy hostname redirections removed; connected app creation disabled by defaultUpdate URLs to My Domain; use External Client App
v65.0Winter '26 (Oct 2025)SupportedNone significant
v64.0Summer '25 (Jun 2025)SupportedAPI versions 21.0-30.0 retiredMinimum version is now v31.0
v63.0Spring '25 (Feb 2025)Supported
v62.0Winter '25 (Oct 2024)Supported
v58.0Spring '24 (Feb 2024)SupportedMin 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 WhenDon't Use WhenUse Instead
Real-time individual record CRUD (<200 records/operation)Data migration or ETL >2,000 recordsBulk API 2.0
Multi-object operations needing atomicity (Composite API)Scheduled batch processing of >10K recordsBulk API 2.0 with scheduled jobs
Interactive user-facing apps needing immediate responseReal-time event notification without pollingPlatform Events or CDC
Exploring/testing API with simple cURL commandsMetadata deployment (field creation, layouts)Metadata API or Tooling API
Small to medium integrations with <50K API calls/dayHigh-frequency polling (>1 call/second sustained)Streaming API or CDC

Important Caveats

Related Units