Throttling: rate limiting API requests
Permissions (part 16) answer "is this request allowed at all?" — throttling answers a different question: "is this client sending requests too fast?" Both matter for a public API; neither one substitutes for the other.
Global throttle rates
# taskapi/settings.py
REST_FRAMEWORK = {
# ... existing settings ...
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon": "20/minute",
"user": "100/minute",
},
}AnonRateThrottle limits unauthenticated requests by IP address; UserRateThrottle limits authenticated requests by user ID, at a separate, typically higher rate — a logged-in user gets more room than an anonymous one, which is the standard shape for a public API that offers a better rate to identified clients. DRF tracks request counts in Django's cache framework, so a working cache backend is a real prerequisite in production — the default local-memory cache works for development but doesn't share state across multiple server processes.
What a throttled client sees
curl -i http://127.0.0.1:8000/api/tasks/HTTP/1.1 429 Too Many Requests
Retry-After: 47
{"detail": "Request was throttled. Expected available in 47 seconds."}429 Too Many Requests — a status code specifically for this situation, distinct from every other client-error code in part 6. The Retry-After header tells a well-behaved client exactly how long to wait, in seconds, before trying again — API clients built to respect it back off automatically instead of hammering the endpoint in a retry loop.
A scoped throttle for one specific endpoint
# taskapi/settings.py
REST_FRAMEWORK = {
# ... existing settings ...
"DEFAULT_THROTTLE_RATES": {
"anon": "20/minute",
"user": "100/minute",
"token-obtain": "5/minute",
},
}from rest_framework.throttling import ScopedRateThrottle
class ThrottledTokenView(obtain_auth_token.__class__):
throttle_classes = [ScopedRateThrottle]
throttle_scope = "token-obtain"Login endpoints are a common brute-force target — a much stricter rate than the general API makes sense specifically there. ScopedRateThrottle reads its limit from throttle_scope instead of the blanket anon/user rates, letting one endpoint (token issuance, here) have its own tighter budget without changing the global default for everything else.
Relying only on AnonRateThrottle/UserRateThrottle to protect an expensive endpoint — one that runs a slow report or a heavy aggregation query — assuming the general rate is protection enough. A genuinely expensive operation usually deserves its own much stricter ScopedRateThrottle, independent of how generous the rest of the API's rate limit is.
Next: the browsable API itself — it's been used throughout this series to test endpoints in a browser, without ever covering what it actually is or why DRF ships it.