Rate Limiting & Validation
Two defenses round out API security: limiting how often a caller may hit you, and never trusting what they send.
Rate limiting
Section titled “Rate limiting”Rate limiting caps requests per client per window, protecting against abuse, runaway clients, and accidental loops. When a caller exceeds the limit, respond 429 Too Many Requests with a Retry-After header telling them when to try again. It is good practice to advertise the budget with RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers.
A common algorithm is the token bucket: each client has a bucket that refills at a steady rate; each request spends a token; an empty bucket means 429.
Validate everything
Section titled “Validate everything”Every value from a client is untrusted until validated. The main risks:
- Injection — never build SQL/commands by string concatenation; use parameterized queries.
- Mass assignment — do not blindly spread a request body onto a model; accept only an allowlist of fields, or a user could set
isAdmin: true. - Oversized payloads — cap request body size and array lengths to avoid resource exhaustion.
- Type and range — validate types, lengths, and ranges with a schema, and reject with
422(see Validation Errors).
| Advantages (Rate Limiting) | Trade-offs |
|---|---|
| Protects against abuse and DDoS | Legitimate traffic spikes can get rejected too |
| Protects against credential stuffing and brute force | Setting the right limit is hard — too strict or too loose |
| Cuts the cost of downstream services like an LLM API | Sharing a limit across a distributed system needs something like Redis |
| Tells the client via headers how much quota is left | A poor error message hurts the user experience |
Common mistakes
Section titled “Common mistakes”Rate limiting without telling the client Symptom:
429 Too Many Requestswith no header saying how much quota is left or when it resets- The client doesn’t know how long to wait — a retry loop makes it worse
- Always send
Retry-After,X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset(or the IETFRateLimit-*equivalents)
Input validation without sanitizing Symptom:
- Accepting
name: "<script>alert(1)</script>"and storing/rendering it unsanitized - XSS when the API response is rendered in a web app
- Validate type/format and sanitize string input before storing it
💡 Real-world examples
GitHub API:
- Rate limit: 5,000 requests/hour for authenticated requests
- Headers:
X-RateLimit-Limit: 5000,X-RateLimit-Remaining: 4999,X-RateLimit-Reset: 1609459200Stripe API:
- Rate limits vary by endpoint —
/chargeshas a different limit than/events429comes with aRetry-Afterheader telling the client how many seconds to wait