DRF Throttling
Protect your API from abuse and runaway clients with rate limiting — anonymous, per-user and scoped.
Throttling answers ‘how often may this client call me?’ It is your first line of defence against scrapers, brute-force login attempts and a buggy client stuck in a retry loop. DRF has a clean built-in system driven by settings.
The three built-in classes
AnonRateThrottle— limits unauthenticated clients by IP.UserRateThrottle— limits authenticated users by account.ScopedRateThrottle— different limits for different endpoints via athrottle_scope.
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day',
},
}The rate string is number/period where period is second, minute, hour or day. When exceeded, DRF returns 429 Too Many Requests with a Retry-After header.
DRF throttling is backed by the cache. On more than one server, point the cache at a shared Redis — a per-process LocMemCache means each worker counts separately and your real limit becomes N× what you configured.
Example
from rest_framework.throttling import ScopedRateThrottle, UserRateThrottle
from rest_framework import viewsets
# A custom throttle keyed on the user's plan, not a flat rate.
class BurstRateThrottle(UserRateThrottle):
scope = 'burst'
class SustainedRateThrottle(UserRateThrottle):
scope = 'sustained'
class ReportViewSet(viewsets.ModelViewSet):
queryset = Report.objects.all()
serializer_class = ReportSerializer
# Combine two windows: short burst + long sustained ceiling.
throttle_classes = [BurstRateThrottle, SustainedRateThrottle]
def get_throttles(self):
# The expensive export action gets its own, tighter scope.
if self.action == 'export':
self.throttle_scope = 'exports'
return [ScopedRateThrottle()]
return super().get_throttles()
# settings.py
# REST_FRAMEWORK = {
# 'DEFAULT_THROTTLE_RATES': {
# 'burst': '60/min', # no more than 60 requests a minute
# 'sustained': '1000/day', # and no more than 1000 a day
# 'exports': '5/hour', # the heavy endpoint is rationed hard
# },
# }
#
# CACHES = {'default': {
# 'BACKEND': 'django.core.cache.backends.redis.RedisCache',
# 'LOCATION': 'redis://cache:6379/1', # shared, so counts are global
# }}When to use it
- A developer applies AnonRateThrottle to limit unauthenticated API requests to 100 per day to deter scraping.
- A team uses UserRateThrottle to cap authenticated requests at 1000/hour to protect against abusive clients.
- A developer writes a custom throttle class to rate-limit POST requests on a registration endpoint to 5 per hour.
More examples
Global throttle settings
Configures global rate limits: 100 requests/day for anonymous users and 1000/hour for authenticated ones.
# settings.py
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon": "100/day",
"user": "1000/hour",
},
}Per-view throttle override
Applies a stricter 10/min burst throttle specifically on the login endpoint to limit brute-force attempts.
from rest_framework.throttling import UserRateThrottle
class BurstRateThrottle(UserRateThrottle):
scope = "burst"
# settings: 'burst': '10/min'
class LoginView(APIView):
throttle_classes = [BurstRateThrottle]
def post(self, request):
...Custom throttle for registration
A custom throttle class that limits account registrations to 5 per hour per IP address.
from rest_framework.throttling import SimpleRateThrottle
class RegistrationThrottle(SimpleRateThrottle):
scope = "registration"
# settings: 'registration': '5/hour'
def get_cache_key(self, request, view):
return self.cache_format % {
"scope": self.scope,
"ident": self.get_ident(request),
}
Discussion