Provider rate limits are not a ceiling you occasionally touch. They are a cliff. Cross it and you get 429 responses, and on some platforms a temporary block that outlasts the burst that caused it. Budgeting your own outbound calls is cheaper than recovering from being throttled.

Know the Actual Numbers

Write the limits down before you write the integration. Stripe allows roughly 100 read and 100 write requests per second in live mode. Shopify’s REST Admin API on a standard plan runs on a leaky bucket of 40 requests with a 2-per-second refill. Slack’s Web API tiers most methods at about 20 calls per minute. These are not interchangeable, and a retry policy tuned for one will destroy your standing with another.

Keep the numbers in the same config file as the credentials. An integration whose limits live only in a vendor’s documentation is an integration nobody will tune correctly under pressure.

Spend a Budget, Not a Retry Policy

The common mistake is treating rate limiting as an error-handling concern: call as fast as possible, catch the 429, back off. That works until two processes do it at once, at which point both back off, both retry, and you have built a synchronized burst generator.

Invert it. Give each provider a token bucket sized just under the documented limit — 80 percent is a reasonable default — and make every outbound call acquire a token first. Calls wait instead of failing. The 429 handler becomes a safety net rather than the mechanism.

One Bucket Per Provider, Shared Across Workers

An in-process limiter is correct only when you run exactly one process. The moment you scale to three workers, each holding a 100-per-second limiter, your effective rate is 300 per second and the provider sees the truth.

Move the counter to shared state. Redis with an atomic increment and expiry is enough: a key per provider per second, incremented on acquire, rejected above the threshold. Under 20 lines, and it makes horizontal scaling safe instead of dangerous. Where the provider scopes limits per account rather than per application, key the bucket by account ID as well, so one heavy tenant cannot starve the rest.

Read the Response Headers

Most providers tell you where you stand. Shopify returns the current bucket usage on every call. GitHub sends remaining quota and a reset timestamp. Stripe surfaces rate-limit signals on 429 responses.

Feed those values back into the limiter rather than trusting your static config. Documented limits change, and per-account limits are sometimes lower than the public number. When a response says 3 of 40 remaining, the right move is to slow down immediately, not to keep spending at the configured rate until something fails.

Separate Interactive Work From Bulk Work

A nightly sync that pulls 50,000 records will consume the entire budget and starve the API call a user is waiting on. Split the quota: reserve a fixed share — 70 percent interactive, 30 percent bulk is a workable split — and run bulk work through a queue with its own slower limiter.

The measurable outcome is that a backfill no longer shows up as user-visible timeouts. That single separation removes the most common cause of “the app is slow” reports that correlate with nothing in your own application metrics.

What to Alert On

Two alerts are sufficient. First, any 429 at all, because with a budget in place a 429 means your model of the limit is wrong. Second, sustained wait time in the limiter above a threshold you choose — a queue that is always full means you need higher limits from the provider or less chatty code, and both take lead time to fix.

Get articles like this by email

Production patterns, checklists, and failure stories for people shipping SaaS. No spam.