~/TechPurAI
~/tutorials/django-rest-framework/throttling
intermediate·part 17 of 22·2 min read

Throttling: rate limiting API requests

Updated Aug 16, 2026Python · Django

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

python
# 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

bash
curl -i http://127.0.0.1:8000/api/tasks/
text
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

python
# taskapi/settings.py
REST_FRAMEWORK = {
    # ... existing settings ...
    "DEFAULT_THROTTLE_RATES": {
        "anon": "20/minute",
        "user": "100/minute",
        "token-obtain": "5/minute",
    },
}
python
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.

Common mistake

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.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous16. Permissions: built-in classes and writing a custom onenext →18. The browsable API: what it is and why it matters