Designing Around Salesforce API Limits

How to architect integrations that survive governor limits and API caps

Posted by Syed Zain Raza

Salesforce is a multi-tenant platform, and the limits are the fence that keeps one org's bad code from degrading everyone else's. You cannot negotiate with them; you can only architect around them. The orgs that hit limit errors in production are almost never doing more work than their limits allow - they are doing the same work inefficiently.

Know Which Limit You Are Actually Fighting

Two different limit families get conflated constantly:

API request limits govern inbound calls from external systems - your integrations, middleware, and tools. The daily cap is shared across ALL connected apps, which is the detail that bites: your enrichment tool, your marketing automation sync, and your BI extract are all drawing from one bucket. When the bucket empties, everything stops, and the tool that caused it is rarely the tool that visibly fails first.

Governor limits constrain what happens inside a single transaction on-platform: 100 SOQL queries, 150 DML statements, CPU time, heap size. These are per-transaction, not daily - hitting them means your code or Flow design is wrong, not that your org is too busy.

The Bulk Pattern: The Fix for 80 Percent of Problems

Most limit pain traces to per-record operations. One query per record, one API call per record, one DML per record. Every platform limit assumes you batch:

// WRONG: query inside loop - dies at 100 records
for (Lead l : leads) {
    Account a = [SELECT Id FROM Account
                 WHERE Website = :l.Website];
}

// RIGHT: one query, map lookup
Set<String> domains = new Set<String>();
for (Lead l : leads) { domains.add(l.Website); }
Map<String, Account> byDomain = new Map<String, Account>();
for (Account a : [SELECT Id, Website FROM Account
                  WHERE Website IN :domains]) {
    byDomain.put(a.Website, a);
}

The same principle applies to external integrations: use the Composite API to bundle up to 25 operations into one call, and the Bulk API 2.0 for anything over a few thousand records - it processes millions of rows for a handful of API requests. If your integration syncs 50,000 records nightly using single-record REST calls, you are spending 50,000 requests on what Bulk API does in a few dozen.

Stop Polling, Start Listening

The single most wasteful integration pattern is polling: "query Salesforce every 5 minutes to see if anything changed." That is 288 API calls a day per object, almost all of which return nothing. Change Data Capture and Platform Events invert the model - Salesforce pushes changes to your subscriber as they happen. Event-driven integrations use a fraction of the API budget, have lower latency, and eliminate the entire class of "we missed records because the poll window shifted" bugs.

Design Rules for Integration Architects

1. Inventory the bucket.  List every tool with API access and its
   daily consumption. Most orgs have at least one tool burning
   30% of the budget for a feature nobody uses.

2. One front door.  Route external writes through middleware or an
   integration layer rather than letting 8 tools write directly.
   One place to batch, throttle, retry, and monitor.

3. Fail toward a queue.  When you approach limits, degrade to
   queued/deferred processing - never drop data silently.

4. Retry with backoff, respect the Retry-After header, and make
   every write idempotent (upsert on external ID) so retries
   are safe.

5. Alert at 70%.  The REST API returns your remaining calls in
   the Sforce-Limit-Info header on every response. Watch it.
   Finding out at 100% means finding out from angry users.

Async Is Your Overflow Valve

On-platform, when synchronous transaction limits pinch, move work async: Queueable Apex for chained operations, Batch Apex for large data volumes (each batch execution gets fresh governor limits), Scheduled Apex for periodic jobs. The design question to ask about any automation is "does this need to happen in the transaction, or just soon after?" Almost everything - scoring, enrichment, sync-out, notifications - is "soon after," and moving it async makes the user-facing save fast and limit-proof at the same time.

The deeper principle: limits are not the enemy of good architecture, they are a forcing function for it. Systems designed around batching, events, queues, and idempotency are not just limit-compliant - they are the systems that scale, recover from failures, and stay debuggable. Salesforce is just making you build them earlier than you planned.