Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

Straight answer: read the Sforce-Limit-Info header on every response, cap your own concurrency before Salesforce caps it for you, and retry with exponential backoff and jitter only on the errors that are worth retrying. Salesforce does not usually answer with a tidy 429. It answers with a 403 carrying REQUEST_LIMIT_EXCEEDED, and by then you are already out of allocation for the rest of the day.

The header nobody reads

REST responses carry your current daily usage:

Sforce-Limit-Info: api-usage=8241/15000

That is free information on a call you were making anyway. Parse it, record it, and act on it. A client that knows it is at 80% of allocation can slow its background work down while letting interactive requests through, which is a far better outcome than discovering the ceiling by hitting it.

The /services/data/vXX.0/limits endpoint gives the full picture across every allocation and does not itself count against the daily request limit, so a monitor can poll it without guilt.

Two different ceilings

The daily request allocation is the one everyone knows: sized by edition and licence count, measured over a rolling 24-hour window rather than resetting at midnight. Because the window rolls, a spike at two in the afternoon is still weighing on you at one the following afternoon.

The concurrency ceiling is the one that bites unexpectedly. Salesforce limits how many long-running synchronous requests an org can have in flight at once, where long-running means taking more than a few seconds. Exceed it and further requests are refused immediately, even though your daily allocation is nowhere near spent. A single badly written report query called by twenty parallel threads will do it.

These fail differently and need different responses. Daily exhaustion means stop until the window rolls. Concurrency exhaustion means back off for seconds and reduce parallelism.

Backoff that works

Exponential, with jitter, with a cap, and with a maximum attempt count. The jitter matters more than people expect: without it, every worker that failed at the same moment retries at the same moment, and you rebuild the exact burst that caused the problem.

delay = min(cap, base * 2 ** attempt)
sleep(random.uniform(0, delay))

Retry on: connection errors, read timeouts, 500, 502, 503, 504, and the row-lock error UNABLE_TO_LOCK_ROW. Retry cautiously on concurrency refusals, with a longer base delay.

Do not retry on: 400 with a validation error, 401, 403 for insufficient privileges, 404. And do not blindly retry REQUEST_LIMIT_EXCEEDED for the daily allocation, because every attempt is another request against a limit you have already exhausted. Fail loudly and let a human decide.

Control concurrency at the source

Backoff is a reaction. Concurrency control is prevention, and it is cheaper.

  • A bounded worker pool. Pick a number of parallel requests and hold to it. Five to ten is plenty for most integrations, and a semaphore around the HTTP client is a ten-line change.
  • A token bucket for the daily budget. Decide how many calls this integration is allowed per hour, and make it wait rather than compete with everything else in the org.
  • Separate the queues. Interactive traffic and bulk background work should not share a pool. When the budget gets tight, the background pool is the one that pauses.
  • A circuit breaker. After a run of failures, stop calling entirely for a period. This is what stops a dead org turning into a retry storm.

Spend fewer calls

The best rate limit strategy is needing less. sObject Collections moves up to 200 records per request. The composite resource chains up to 25 subrequests into one round trip. Anything above tens of thousands of records belongs in Bulk API 2.0, which draws on a separate record allocation rather than your request count. A nightly job moved from row-by-row REST to Bulk can free most of an org’s daily budget in an afternoon’s work.

Frequently asked

Does Salesforce send Retry-After? Not reliably. Honour it when present, and fall back to your own backoff when it is not.

Can I get the limit raised? Temporary increases are available through support, and additional capacity can be purchased. Neither is a substitute for finding the integration that is making a hundred thousand single-record calls.

Do failed calls count? A request refused for exceeding the limit still reached Salesforce. Assume it counts and stop calling.

What about Apex callouts going the other way? Different limits entirely: callouts per transaction and total callout time, enforced per transaction rather than per org per day.