HTTP 429 is Too Many Requests: the client has sent too many requests in a given amount of time. It isn’t in RFC 9110; RFC 6585 section 4 defines it. The response should explain the condition and may carry Retry-After saying how long to wait. RFC 6585 deliberately leaves open how the server identifies the client and how it counts.
The exchange
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/problem+json
{"title": "Rate limit exceeded", "status": 429, "detail": "100 requests per minute; try again in 30 seconds"}
Retry-After is either a number of seconds or an HTTP date such as Fri, 16 Oct 2026 00:00:00 GMT (RFC 9110 section 10.2.3). Many APIs also report their counters in headers like X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Those are conventions, not a standard, so check each API’s documentation for what the reset value counts.
What the client should do
Wait. Honor Retry-After when it’s there; otherwise back off exponentially with random jitter, so a fleet of clients doesn’t retry in lockstep, and cap the number of attempts. A tight retry loop only keeps you over the limit. Not every API uses 429 for this: GitHub’s REST API answers a spent rate limit with 403 or 429 and x-ratelimit-remaining: 0.
Caching
RFC 6585 says a response with 429 must not be stored by a cache, so a CDN shouldn’t hand one client’s 429 to another.
Search engines
Google treats 429 as a server error, not as a 4xx: Googlebot slows down, and pages already indexed stay unless the errors go on. That makes 429 or 503 the way to ask a crawler to ease off, where 401 or 403 would get pages dropped.
Who sends it, and who sends something else
- AWS API Gateway throttling answers 429.
- An AWS WAF rate-based rule blocks with 403 unless you configure a custom response.
- nginx’s
limit_reqandlimit_connreject with 503 unless you setlimit_req_statusandlimit_conn_status. - Kubernetes ingress-nginx’s
nginx.ingress.kubernetes.io/limit-rpsannotation also rejects with 503, unless the controller’s ConfigMap setslimit-req-status-code: "429".
A rate limit is the client’s doing and 429 says so; a 503 tells the client the server is in trouble.
In your stack
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;
}
}
import { rateLimit } from 'express-rate-limit';
app.use('/api', rateLimit({ windowMs: 60_000, limit: 100 })); // Express: 429 past 100 a minute
raise HTTPException(status_code=429, detail="Rate limit exceeded",
headers={"Retry-After": "30"}) # FastAPI
w.Header().Set("Retry-After", "30")
http.Error(w, "rate limit exceeded", http.StatusTooManyRequests)
Search 429 503 in the tool below to compare the two.