API Design and Rate Limiting
REST API basics
Model resources with nouns and use HTTP methods for actions.
| Operation | Request | Success |
|---|---|---|
| List | GET /users | 200 |
| Read | GET /users/42 | 200 |
| Create | POST /users | 201 + Location |
| Replace | PUT /users/42 | 200 / 204 |
| Update | PATCH /users/42 | 200 |
| Delete | DELETE /users/42 | 204 |
Good practices
- Version the API (
/v1/...) and keep changes backwards compatible. - Return consistent error bodies and correct status codes.
- Use idempotency keys for non-idempotent operations such as payments.
- Authenticate with tokens (OAuth 2, JWT) and always use HTTPS.
Pagination
| Style | Pros | Cons |
|---|---|---|
| Offset / limit | Simple, jump to a page | Slow on large offsets, unstable when data changes |
| Cursor (keyset) | Fast, stable | Cannot jump to arbitrary pages |
GET /posts?limit=20&cursor=eyJpZCI6MTIzfQ
Rate limiting
Protects services from abuse and overload. Respond with 429 Too Many Requests and a Retry-After header.
| Algorithm | Idea | Notes |
|---|---|---|
| Fixed window | Count per time window | Burst at window edges |
| Sliding window | Smooth count over a moving window | More accurate, more memory |
| Token bucket | Tokens refill at a rate; each request takes one | Allows bursts, widely used |
| Leaky bucket | Requests drain at a constant rate | Smooths traffic |
token_bucket.py
import time
class TokenBucket:
def __init__(self, rate, capacity):
self.rate, self.capacity = rate, capacity
self.tokens, self.last = capacity, time.monotonic()
def allow(self):
now = time.monotonic()
self.tokens = min(self.capacity,
self.tokens + (now - self.last) * self.rate)
self.last = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
In a distributed setup keep counters in a shared store (for example Redis with atomic operations), keyed by user, API key or IP.