Home / Blog / API Client
API ClientAPI Rate Limiting: How to Handle 429 Errors

If you have ever hammered an API with a loop and suddenly started getting HTTP 429 responses, you have met rate limiting. It is one of the most common reasons a request that worked five minutes ago starts failing, and one of the most misunderstood. This guide explains how rate limiting actually works, the algorithms providers use behind the scenes, the exact status code and headers you should read, and how to observe and test rate-limited behavior from an API client.
What Rate Limiting Is and Why APIs Use It
Rate limiting is a hard cap on how many requests a client may send to an API within a defined window of time. Once you cross the cap, the API stops serving you and returns an error until the window resets. APIs enforce limits for a few practical reasons:
- Stability. A single misbehaving client should not be able to exhaust shared capacity and degrade the service for everyone else.
- Fairness. Limits keep one tenant from monopolizing resources on a multi-tenant platform.
- Cost control. Every request consumes compute, bandwidth, and downstream quota. Caps keep usage predictable.
- Abuse prevention. Limits blunt credential-stuffing, scraping, and brute-force attempts.
The Algorithms Behind Rate Limiting
Different gateways implement limits with different counters. The four you will meet most often are:
Fixed window
The simplest model. The server counts requests in a fixed clock interval - say 1,000 requests per minute - and resets the counter at the top of each minute. It is cheap to implement but suffers from boundary bursts: a client can send 1,000 requests at 12:00:59 and another 1,000 at 12:01:00, briefly doubling the intended rate.
Sliding window
A refinement that smooths the boundary problem by tracking requests over a rolling interval rather than a fixed clock tick. Instead of resetting abruptly, the window continuously slides, so the count always reflects the trailing N seconds. It is more accurate at the cost of more bookkeeping.
Token bucket
A bucket holds tokens up to a maximum capacity, and tokens refill at a steady rate. Each request consumes one token; if the bucket is empty, the request is rejected. Because the bucket can hold a reserve, token bucket tolerates short bursts while still enforcing a long-run average rate. This is the model most commonly associated with public APIs.
Leaky bucket
Requests enter a queue (the bucket) and are processed - or leak out - at a constant rate. If requests arrive faster than the leak rate, the bucket fills and overflowing requests are dropped. Leaky bucket emphasizes a smooth, constant output rate, which makes it closer in spirit to throttling than to a hard reject cap.
The 429 Too Many Requests Status Code
When you exceed a limit, a well-behaved API answers with 429 Too Many Requests. The body usually carries a short explanation, and the headers tell you when you can try again. A typical exchange looks like this:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
{
"error": "rate_limit_exceeded",
"message": "API rate limit exceeded. Retry after 30 seconds."
}
The Headers That Matter
Rate-limited APIs communicate quota state through response headers. Read these before you retry blindly:
- Retry-After - how long to wait before sending another request. It is either a number of seconds (
Retry-After: 30) or an HTTP date. This is the single most important header to honor. - RateLimit-Limit - the maximum number of requests allowed in the current window.
- RateLimit-Remaining - how many requests you have left in the window. When this hits 0, the next request will be a 429.
- RateLimit-Reset - when the window resets, expressed as seconds remaining or a Unix timestamp depending on the provider.
Header naming is not fully standardized. You will also see vendor-prefixed variants such as X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. The IETF RateLimit-* family is the direction the ecosystem is converging on, but always check the specific API’s docs.
Inspect rate limits live: Send a request, watch the status flip to 429, and read every RateLimit-* header in one place with Try Requestly API Client →
Observing and Testing Rate Limits From an API Client
Before you write retry logic into production code, it pays to observe how a specific API behaves. An API client like Requestly is exactly the right tool for this: you send the request, see the response status and headers, and can codify the expected behavior as a post-response test. Requestly is the client you use to inspect and test rate-limited APIs - it does not impose limits of its own.
In a post-response script you read the status with rq.response.code and assert against it. Here is a test that confirms a 429 is returned and that the rate-limit headers are present and sane:
// Post-response test: verify rate-limit behavior
rq.test("Returns 429 when over the limit", function () {
rq.expect(rq.response.code).to.equal(429);
});
// Header lookup helper (headers are exposed as an array)
function header(name) {
var found = rq.response.headers.find(function (h) {
return h.key.toLowerCase() === name.toLowerCase();
});
return found ? found.value : null;
}
rq.test("Retry-After header is present", function () {
rq.expect(header("Retry-After")).to.be.ok;
});
rq.test("Remaining quota is exhausted", function () {
rq.expect(header("RateLimit-Remaining")).to.equal("0");
});
You can also capture the suggested wait into an environment variable so a later request - or just your own notes - can reference it:
var retryAfter = rq.response.headers.find(function (h) {
return h.key.toLowerCase() === "retry-after";
});
if (retryAfter) {
rq.environment.set("retryAfter", retryAfter.value);
}
For more patterns like this, see our walkthrough of API assertions and tests and the broader set of practical script examples for API testing.
Handling 429s in Your Application Code
Inspecting a limit is one thing; surviving it in production is another. The standard answer is exponential backoff with jitter: when you hit a 429, wait, retry, and double the wait each time it fails again, with a little randomness so a fleet of clients does not retry in lockstep. Honor Retry-After when the server provides it. In application code this looks roughly like:
// Consumer-side retry with exponential backoff + jitter
async function callWithBackoff(doRequest, maxRetries = 5) {
let attempt = 0;
while (true) {
const res = await doRequest();
if (res.status !== 429) return res;
if (attempt >= maxRetries) throw new Error("Rate limited: out of retries");
// Prefer the server's hint, else exponential backoff
const retryAfter = Number(res.headers.get("Retry-After"));
const backoff = Number.isFinite(retryAfter)
? retryAfter * 1000
: Math.min(2 ** attempt * 1000, 30000);
const jitter = Math.random() * 250;
await new Promise((r) => setTimeout(r, backoff + jitter));
attempt++;
}
}
An important honesty note about scripts: the backoff loop above belongs in your application or consumer code, not in a Requestly script. Pre-request and post-response scripts cannot make their own HTTP calls and cannot sleep or use async/await - they run synchronously around a single request. Use scripts to assert the 429 and read the headers; implement the actual retry loop in the service or SDK that consumes the API. If your retries start returning 401s instead of 429s, that is a different problem - see fixing 401 Unauthorized errors.
Practical Tips
- Always read
RateLimit-Remainingproactively and slow down before you hit zero, rather than waiting for the 429. - Honor
Retry-Afterexactly. Retrying earlier just burns more quota and can extend a penalty window. - Add jitter to backoff to avoid the thundering-herd effect when many clients recover at once.
- Cap your maximum backoff and your retry count - infinite retries hide real outages.
- Cache responses where you can. The cheapest request is the one you never send.
Rate limiting is closely related to, but distinct from, throttling. If you are unsure which behavior you are seeing, read API throttling vs rate limiting next.
Frequently Asked Questions
What is the 429 Too Many Requests status code?
429 Too Many Requests is the HTTP status an API returns when a client has sent more requests than the rate limit allows within the current window. The response typically includes a Retry-After header telling you how long to wait before trying again.
What is the difference between Retry-After and RateLimit-Reset?
Retry-After tells you how long to wait before sending any new request after a 429, either as seconds or an HTTP date. RateLimit-Reset tells you when the current quota window resets, usually as seconds remaining or a Unix timestamp. Retry-After is the one to honor immediately after being limited.
How do I read a 429 response in an API client?
In a post-response script you check rq.response.code for 429 and inspect the response headers array for Retry-After and the RateLimit-* values. You can assert the expected behavior with rq.test and rq.expect, and store the wait time with rq.environment.set.
Can Requestly scripts automatically retry after a 429?
No. Requestly pre-request and post-response scripts run synchronously around a single request and cannot make their own HTTP calls or sleep with async/await. Use scripts to assert the 429 and read the headers, and put the actual exponential-backoff retry loop in your application or SDK code.
What algorithms do APIs use to enforce rate limits?
Common algorithms are fixed window, sliding window, token bucket, and leaky bucket. Token bucket is popular because it tolerates short bursts while enforcing a long-run average, while leaky bucket emphasizes a smooth constant output rate.
What is exponential backoff?
Exponential backoff is a retry strategy where you wait progressively longer between attempts - typically doubling the delay each time - after a request fails with a 429. Adding random jitter prevents many clients from retrying in lockstep, and you should still honor the server’s Retry-After hint when present.
Test any API request visually: import a cURL command or build from scratch in Requestly, the free API client for developers.
Download Free →