Quick answer
Rate limiting controls how many requests a client can make in a period of time. It protects APIs from abuse, accidental loops, expensive traffic spikes, and unfair usage across customers.
When a client exceeds the limit, the API usually returns 429 Too Many Requests and includes response headers that explain when the client can retry.
Why rate limiting matters
Backend systems are shared resources. One noisy client can consume database connections, CPU, queue capacity, third-party API quota, or cache bandwidth that other clients need.
Rate limiting gives the API a predictable rule before the system is already overloaded. It also improves product fairness: a free plan, paid plan, internal job, and partner integration may each need different limits.
What to limit by
A rate limit needs an identity key. Common choices include:
| Key | Use case | Caveat |
|---|---|---|
| API key | Public or partner APIs. | Key rotation and shared keys need care. |
| User ID | Authenticated user actions. | One account can still have many users. |
| Account or tenant ID | SaaS plan limits. | Heavy users in one tenant share capacity. |
| IP address | Anonymous traffic. | NAT, proxies, and mobile networks can group users together. |
| Endpoint | Expensive operations. | Usually combined with another identity key. |
For many production APIs, rate limiting is scoped by both actor and endpoint. For example, account:123:/api/exports.
Fixed window
A fixed window limit counts requests in a time bucket.
Limit: 100 requests per minute
Window: 12:00:00 to 12:00:59
It is simple to understand and cheap to implement. The downside is burstiness at the boundary. A client can send 100 requests at 12:00:59 and 100 more at 12:01:00.
Fixed windows are fine for many low-risk limits, especially when simplicity matters.
Sliding window
A sliding window smooths the boundary by considering a rolling period instead of a hard calendar bucket.
Allow 100 requests in the previous 60 seconds.
This is fairer than a fixed window but can require more storage or more complex approximation. Some implementations keep request timestamps. Others use weighted counters from the current and previous bucket.
Token bucket
A token bucket allows bursts while enforcing a long-term rate.
Think of a bucket that refills with tokens over time. Each request consumes a token. If the bucket is empty, the request is limited.
Capacity: 50 tokens
Refill: 10 tokens per second
This lets a client make a short burst up to the bucket capacity, then continue at the refill rate. Token buckets are common for APIs because real traffic is naturally uneven.
429 response example
A rate-limited response should be predictable:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 1719859230
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Try again later.",
"retryAfterSeconds": 30
}
Use REST API Status Codes Explained for broader guidance on 429 and other API response codes.
Client retry behavior
Clients should not immediately retry a 429 response in a tight loop. They should respect Retry-After when present, use backoff, and avoid replaying non-idempotent operations blindly.
If the limited request creates a side effect, combine retries with Idempotency in APIs Explained so duplicate attempts do not create duplicate orders, jobs, or payments.
Where to enforce limits
Rate limiting can happen at several layers:
- CDN or edge layer for broad traffic protection.
- API gateway for centralized enforcement across services.
- Application service for business-aware limits.
- Database or queue layer for expensive workflows.
Edge and gateway limits are good for coarse protection. Application-level limits are better when the rule depends on user plan, account state, endpoint cost, or domain-specific behavior.
Storage choices
A rate limiter needs shared state when multiple instances handle traffic.
In-memory counters are useful for local development and single-process tools, but they break across deployments. Production systems usually use Redis, a gateway-managed store, a database, or a platform primitive near the edge.
The store should support atomic increment or compare-and-update operations. Without atomicity, concurrent requests can slip through the limit.
Choosing limits
Start with the purpose of the limit:
- Protect login and password reset endpoints from abuse.
- Prevent expensive exports from overwhelming workers.
- Enforce plan limits for public APIs.
- Keep webhook ingestion stable.
- Protect third-party dependency quotas.
Then pick a rate, burst capacity, and response contract that clients can understand. Limits should be observable through metrics and logs so you can tune them with real traffic.
Common mistakes
The first mistake is returning a generic 500 or 403 when a client is limited. Use 429 so clients, SDKs, and monitoring tools know what happened.
The second mistake is limiting only by IP address for authenticated APIs. Shared networks can punish unrelated users, while attackers may rotate IPs.
The third mistake is forgetting internal traffic. Background jobs, imports, and retries can exceed limits just like public clients.
The fourth mistake is logging every limited request without sampling. During an incident, that can create another overload path.
Related reading
Read API Abuse Prevention Explained for turning rate limits into a broader abuse-control model, REST API Status Codes Explained for 429, 503, and retry behavior, and Idempotency in APIs Explained before adding retries to side-effecting endpoints.
Read Security Event Logging Explained for logging limit hits safely, API Gateway Explained for centralized enforcement, and API Key Design Best Practices when deciding which credential should own a quota. Use the Unix Timestamp Converter when debugging reset times in rate-limit headers, and use the HTTP Status Code Lookup as a quick reference while designing response behavior. For the full sequence, browse the API Design Learning Path and the Backend Security Learning Path.