API Rate Limiting Best Practices for Providers and Clients
August 29, 2026


Rate limiting fails in two directions. Providers either lock down their API so hard that legitimate bursts get punished, or leave it so loose that a handful of misbehaving clients take the whole system down. Consumers fail just as often — retrying blindly into a 429, hammering an endpoint the moment a limit resets, or confusing "too many requests" with "too many concurrent operations." Good API rate limiting best practices treat this as a contract with obligations on both sides, not a one-way policy handed down from the provider.
Why Rate Limiting Matters (For Providers and Consumers)
For the provider, a rate limit is infrastructure protection: it caps worst-case load, keeps one noisy tenant from starving everyone else, and gives capacity planning a predictable ceiling. For the consumer, a well-designed limit is a promise — if you follow the stated rules, your traffic won't be dropped without warning, and you'll get consistent, honest feedback when you're close to the edge.
Get either side wrong and the costs are concrete. Providers who under-limit see cascading outages when one client's retry storm degrades service for everyone. Consumers who mishandle 429s turn a brief throttle into a self-inflicted outage — retry loops that fire faster than the limit resets, amplifying load right when the system is already stressed. Both failure modes are avoidable with a handful of well-understood patterns.
Choosing a Rate Limiting Algorithm
Most rate limiting algorithms fall into four families, and picking the wrong one is usually what causes uneven or unfair throttling.
Fixed window counter resets a count every interval (e.g., 100 requests per minute, clock-aligned). It's cheap to implement but has a well-known boundary problem: a client can burst 100 requests at 0:59 and another 100 at 1:00, doubling the effective rate for a brief window (Azion's explanation of the boundary issue covers this well).
Sliding window — either as a log of timestamps or a weighted counter blending the current and previous window — smooths that edge case out at the cost of slightly more computation and state.
Token bucket allows tokens to accumulate up to a cap and lets clients spend them in a burst, then refills at a steady rate. It's the natural choice when occasional bursts are legitimate — a batch job that fires ten requests at once, then goes quiet.
Leaky bucket enforces a constant outflow regardless of how requests arrive, which suits systems where downstream processing genuinely can't handle bursts at any rate.
Rule of thumb: if bursts represent real, legitimate usage, favor token bucket; if you need a strictly smoothed rate regardless of arrival pattern, leaky bucket or sliding window is safer. Arcjet's comparison is a good reference when weighing token bucket vs sliding window for a specific API shape — read APIs with spiky legitimate traffic usually want the former; billing or write-heavy APIs usually want the latter.
Designing Limits That Don't Punish Good Clients
Providers should separate per-key limits from per-IP limits — API keys identify a customer reliably, while IPs get shared behind NATs and proxies and punish innocent neighbors. Keys are also the natural place to attach rotation practices, since a leaked or over-shared key is often the actual cause of an unexpected throttle.
Tiered limits by plan are standard — free tier gets a conservative ceiling, paid tiers scale up — but the more consequential decision is separating limits by cost, not just by customer. An endpoint that renders a full page in a headless browser costs orders of magnitude more CPU and memory than one that returns a cached JSON blob; it deserves its own, tighter limit rather than sharing a bucket with cheap endpoints.
This is also where concurrency limits vs request limits stop being the same conversation. A rate limit governs how many requests arrive over time; a concurrency limit governs how many are in flight at once. An API can be well within its requests-per-minute budget and still be overloaded if every request holds a resource — a database connection, a browser tab — for several seconds. Effective API throttling strategies track both dimensions independently and expose both to the client.
Whatever the limiter, return standard rate limit headers so clients don't have to guess. The IETF has a draft standard for RateLimit and RateLimit-Policy headers that's increasingly the reference point instead of each provider inventing its own X-RateLimit-* variant. At minimum, expose the limit, remaining quota, and reset time — and a separate concurrency counter if that's a distinct constraint.
Handling 429s as an API Consumer
A 429 is not an error to swallow and retry immediately — it's structured feedback, and ignoring the structure is what turns a brief throttle into a client-side outage.
First, respect Retry-After if it's present; it tells you exactly how long the server wants you to wait, and guessing a shorter interval defeats the purpose of the header. Second, when no explicit delay is given, implement exponential backoff with jitter rather than a fixed retry interval — double the wait on each successive failure, but add randomness so a fleet of clients hitting the same limit doesn't retry in lockstep and recreate the same spike. A minimal version: start at a base delay, double it each attempt up to a cap, then add a random offset before sleeping.
Third, stop treating retries as a naive loop and instead run requests through a concurrency pool or queue — cap how many requests are in flight, and let the queue absorb backpressure instead of firing new attempts the moment one fails. Finally, log and alert on sustained throttling. A single 429 is normal traffic shaping; a client stuck retrying against the same limit for minutes is a signal that something upstream — a burst job, a misconfigured worker, a leaked key — needs fixing at the source, not just retried around.
Rate Limiting for Rendering and Scraping Workloads
Everything above holds for a typical REST API, but headless browser and scraping workloads add a wrinkle: concurrency limits usually matter more than raw request-per-second math. Each render holds a browser context — memory, a CPU-bound page load, sometimes JavaScript execution and network waterfalls — for seconds, not milliseconds. A provider can hit its concurrency ceiling with a fraction of the traffic that would trip a comparable JSON API's rate limit.
That changes what a well-behaved client looks like. Rather than pacing requests against a per-minute number, size a concurrency pool to match the documented concurrent-session limit for your plan, queue anything beyond that locally, and apply the same backoff-with-jitter pattern when a 429 (or a concurrency-specific rejection) comes back. Teams self-hosting Puppeteer often discover this the hard way once traffic grows past what a single instance can hold open at once — worth a look at the trade-offs between running your own browser infrastructure and using a managed API if you're at that inflection point.
Browsevra's own headless rendering API applies this same discipline — separate rate and concurrency limits by plan, standard headers so you can build the client-side logic above without guesswork, and clear documentation for screenshots, PDF generation, HTML rendering, and structured extraction. If you're building against it, the docs cover the concurrency and rate limit specifics per plan, and pricing breaks down what each tier allows. For rendering workloads specifically, respecting concurrency — not just request counts — is what keeps both your integration and browsevra's infrastructure stable under load.
Frequently Asked Questions
What's the difference between rate limiting and throttling?
Rate limiting is the policy — a defined cap on requests or concurrent operations over a given window. Throttling is the enforcement action taken when that cap is exceeded, such as delaying, queuing, or rejecting a request. The terms overlap heavily, but "throttling" more precisely describes the server's runtime response to a limit being reached.
What HTTP status code should an API return when a client is rate limited?
429 Too Many Requests is the standard status code, defined for exactly this purpose. It should be paired with a Retry-After header and, ideally, the RateLimit headers describing remaining quota and reset time so the client knows how to proceed.
Should I retry immediately after getting a 429 error?
No — retrying immediately usually recreates the same overload that triggered the 429 in the first place. Respect the Retry-After header if present, or fall back to exponential backoff with jitter if it isn't, so retries spread out over time instead of clustering.
What's the difference between a rate limit and a concurrency limit?
A rate limit caps how many requests can be made over a time window, while a concurrency limit caps how many operations can run simultaneously. This distinction is critical for resource-heavy workloads like browser rendering, where a request can stay "in flight" for seconds — a client can be well under its rate limit and still get rejected for exceeding concurrency.
Is there a standard header format for communicating rate limits?
Not yet formally finalized, but the IETF's draft RateLimit header specification is becoming the reference point most providers converge toward. It defines RateLimit and RateLimit-Policy headers to communicate limit, remaining quota, and reset time in a consistent format, replacing the inconsistent X-RateLimit-* conventions each provider used to invent independently.
How do I choose between token bucket and sliding window for my API?
Choose token bucket if legitimate traffic arrives in bursts you want to accommodate, since it allows saved-up capacity to be spent quickly. Choose sliding window if you need a strictly smoothed rate that avoids the boundary-burst problem inherent to fixed windows, particularly for write-heavy or billing-sensitive endpoints where evenness matters more than burst tolerance.