HTTP status code · Client errors (4xx)
429 Too Many Requests
429 Too Many Requests means you sent more requests than the server allows in a given time window, so it is rate limiting you. The fix is to wait: honor the Retry-After header if present, and slow down the client that is sending requests too fast.
| Class | 4xx, Client errors |
|---|---|
| Defined in | RFC 6585 §4 |
| Cacheable by default | No; RFC 6585 says caches must not store it |
| Safe to retry | Yes, after the delay in Retry-After, or with exponential backoff and jitter if there is none |
| Relevant headers |
|
What 429 means
RFC 6585, section 4, defines 429 and deliberately leaves the policy to the server: it does not say how to identify the user or count requests. Limits may be per IP, per API key, per account, per endpoint, or shared across a whole cluster, and the same client can be under several at once. The RFC suggests Retry-After and a body explaining the limit, and forbids caches from storing the response.
There is no standard header for the quota itself yet. Many APIs send X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and the IETF is standardizing RateLimit and RateLimit-Policy fields in a draft (draft-ietf-httpapi-ratelimit-headers). Reading those headers lets a client slow down before it hits the wall instead of after.
Not every rate limiter answers 429. nginx limit_req rejects with 503 unless you set limit_req_status 429, and some WAFs and CDNs use 403 or their own challenge pages for the same situation.
Common causes
If you are visiting the site
- Refreshing a page, retrying a login or clicking "resend code" many times in a short period.
- A shared IP address (office, university, mobile carrier NAT, VPN exit node) where other people use up the per-IP limit.
- A browser extension, download manager or scraper hitting the site in the background.
If you run the server
- An API client runs requests in a tight loop or with unbounded concurrency, for example Promise.all over thousands of items.
- Retries without backoff: every failure triggers an immediate retry, which multiplies the request rate exactly when the server is pushing back.
- Many workers or serverless instances share one API key, so their combined rate exceeds a limit that each one respects on its own.
- Your own limiter is keyed on the proxy address instead of the client IP, so every visitor behind the load balancer shares one bucket.
How to fix it
If you are visiting the site
- Stop retrying and wait. Many limits reset within a minute; login and SMS code limits can last 15 minutes to an hour.
- If you are on a VPN or shared network, switch networks or turn off the VPN.
- Disable extensions that prefetch or scrape pages, then try again.
If you run the server
- Honor Retry-After exactly when it is present; it is either a number of seconds or an HTTP date.
- Without Retry-After, back off exponentially with jitter: wait a random time between 0 and min(cap, base x 2^attempt), so a fleet of clients does not retry in sync.
- Cap concurrency (a queue or a pool of N in-flight requests) and watch X-RateLimit-Remaining to slow down before you run out.
- Behind a proxy, key your limiter on the real client IP (Express: app.set("trust proxy", 1)) and return 429 with Retry-After, not 503 or 403.
How to send 429
express-rate-limit sends 429 by default. For your own client code, the curl line at the end shows the behavior to copy: retry on 429, wait as long as Retry-After says, and double the delay otherwise.
import { rateLimit } from 'express-rate-limit';
// Over the limit, the middleware answers 429 with Retry-After
app.use('/api', rateLimit({
windowMs: 60 * 1000,
limit: 100, // requests per client per window
standardHeaders: 'draft-8', // RateLimit + RateLimit-Policy headers
legacyHeaders: false
}));// app/api/search/route.ts
export async function GET() {
return Response.json(
{ error: 'Rate limit exceeded, retry in 60 seconds' },
{ status: 429, headers: { 'Retry-After': '60' } }
);
}mux.HandleFunc("GET /api/search", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Retry-After", "60")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusTooManyRequests) // 429
w.Write([]byte(`{"error":"Rate limit exceeded, retry in 60 seconds"}`))
})from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/api/search")
def search():
raise HTTPException(status_code=429, detail="Rate limit exceeded, retry in 60 seconds", headers={"Retry-After": "60"})limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429; # the default is 503
proxy_pass http://app;
}
}# curl treats 429 as transient: it retries with doubling delays
# and honors Retry-After (curl 7.66.0 and later)
curl --retry 5 --retry-max-time 300 https://api.example.com/search?q=httpCommonly confused with
- 429 vs 503
- 503 says the whole service is overloaded or down for everyone; 429 says this particular client exceeded its own quota while others are served normally.
- 429 vs 403
- 403 is a refusal that waiting will not change; 429 is temporary and clears once the rate-limit window resets.
- 429 vs 402
- Some APIs answer 402 when a paid monthly quota is used up; 429 is for short-term request rate, and waiting fixes it.
Frequently asked questions
- How long do I have to wait after a 429 error?
- Check the Retry-After header: it gives the number of seconds or a date. Without it, the wait depends on the service; many API limits reset every minute, while login and verification-code limits often last 15 to 60 minutes.
- Is 429 Too Many Requests a ban?
- No, it is temporary by design and lifts when the window resets. If it never goes away even after waiting, you may be looking at a longer block from a firewall, which usually shows up as 403 instead.
- How should an API client handle 429?
- Stop sending, wait for the Retry-After delay, then retry. If there is no Retry-After, use exponential backoff with jitter and a maximum number of attempts, and reduce concurrency so the same limit is not hit again right away.
- Why do I get 429 on ChatGPT, Instagram or other big sites?
- Large services apply per-account and per-IP limits on messages, logins and actions. Hitting one, or sharing an IP with many other users through a VPN or carrier network, produces 429 or a "too many requests" message until the limit resets.
- Should my API return 429 or 503 when rate limiting?
- Return 429 with Retry-After when a specific client is over its quota. Use 503 only when the whole service cannot accept work. nginx limit_req uses 503 by default, so set limit_req_status 429.
Last reviewed by Arielton Oberek.