Overview
Throttling controls the rate of requests that clients can make to an API. Throttles indicate a temporary state and are used to implement rate limiting.
Throttling is similar to permissions, but determines if a request should be authorized based on the rate of requests.
Application-level throttling is not a security measure. Malicious actors can spoof IP addresses. Use throttling for implementing business policies and basic protections against service over-use, not for security or DDoS protection.
Configuration
Global Settings
Set throttling policy globally using DEFAULT_THROTTLE_CLASSES and DEFAULT_THROTTLE_RATES:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day'
}
}
Rate Format:
- Format:
number/period
- Periods:
s, m, h, d (or second, minute, hour, day, sec, min, hr)
- Examples:
100/day, 60/min, 1000/hour
Per-View Settings
Set throttling on individual views:
from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView
class ExampleView(APIView):
throttle_classes = [UserRateThrottle]
def get(self, request):
return Response({'status': 'request was permitted'})
Or with function-based views:
from rest_framework.decorators import throttle_classes
@api_view(['GET'])
@throttle_classes([UserRateThrottle])
def example_view(request):
return Response({'status': 'request was permitted'})
Per-Action Settings
Set throttling for specific actions:
from rest_framework.decorators import action
class MyViewSet(viewsets.ModelViewSet):
@action(detail=True, methods=["post"], throttle_classes=[UserRateThrottle])
def example_action(self, request, pk=None):
return Response({'status': 'request was permitted'})
Base Classes
BaseThrottle
All throttle classes extend BaseThrottle.
class BaseThrottle:
def allow_request(self, request, view):
"""
Return True if request should be allowed, False otherwise.
"""
raise NotImplementedError('.allow_request() must be overridden')
def get_ident(self, request):
"""
Identify the machine making the request.
Uses X-Forwarded-For if present, otherwise REMOTE_ADDR.
"""
# Implementation handles NUM_PROXIES setting
...
def wait(self):
"""
Return recommended seconds to wait before next request.
Return None if not implemented.
"""
return None
SimpleRateThrottle
Base class for rate throttles using cache. Subclasses must override get_cache_key().
class SimpleRateThrottle(BaseThrottle):
cache = default_cache
timer = time.time
cache_format = 'throttle_%(scope)s_%(ident)s'
scope = None
THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
def get_cache_key(self, request, view):
"""
Return unique cache key for throttling.
Return None if request should not be throttled.
"""
raise NotImplementedError('.get_cache_key() must be overridden')
Attributes:
cache
cache
default:"default_cache"
Django cache backend to use for storing request history
timer
callable
default:"time.time"
Callable that returns current time in seconds
cache_format
string
default:"'throttle_%(scope)s_%(ident)s'"
Format string for cache keys
Scope name used to look up throttle rate in DEFAULT_THROTTLE_RATES
Override rate instead of using scope (e.g., ‘100/day’)
Built-in Throttle Classes
AnonRateThrottle
Throttles unauthenticated users by IP address. Authenticated users are not throttled.
from rest_framework.throttling import AnonRateThrottle
class MyView(APIView):
throttle_classes = [AnonRateThrottle]
Configuration:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
}
}
Attributes:
Default scope for rate lookup
Suitable for restricting requests from unknown sources.
UserRateThrottle
Throttles authenticated users by user ID. Unauthenticated users are throttled by IP address.
from rest_framework.throttling import UserRateThrottle
class MyView(APIView):
throttle_classes = [UserRateThrottle]
Configuration:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'user': '1000/day',
}
}
Attributes:
Default scope for rate lookup
Multiple User Throttles:
class BurstRateThrottle(UserRateThrottle):
scope = 'burst'
class SustainedRateThrottle(UserRateThrottle):
scope = 'sustained'
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'example.throttles.BurstRateThrottle',
'example.throttles.SustainedRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'burst': '60/min',
'sustained': '1000/day'
}
}
Suitable for simple global rate restrictions per user.
ScopedRateThrottle
Restricts access to specific parts of the API. Only applied to views with a .throttle_scope property.
from rest_framework.throttling import ScopedRateThrottle
class ContactListView(APIView):
throttle_classes = [ScopedRateThrottle]
throttle_scope = 'contacts'
class UploadView(APIView):
throttle_classes = [ScopedRateThrottle]
throttle_scope = 'uploads'
Configuration:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.ScopedRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'contacts': '1000/day',
'uploads': '20/day'
}
}
Attributes:
scope_attr
string
default:"'throttle_scope'"
View attribute name to read scope from
Client Identification
Clients are identified using:
X-Forwarded-For HTTP header (if present)
REMOTE_ADDR WSGI variable (fallback)
NUM_PROXIES Setting
Configure how many proxies are in front of your API:
REST_FRAMEWORK = {
'NUM_PROXIES': 1, # Number of proxies
}
None - Use all of X-Forwarded-For
0 - Use REMOTE_ADDR only
N - Use Nth-to-last address from X-Forwarded-For
Clients behind a NAT gateway will be treated as a single client when NUM_PROXIES is configured.
Cache Configuration
Throttle classes use Django’s cache backend.
Default Setup:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
}
}
Custom Cache:
from django.core.cache import caches
class CustomAnonRateThrottle(AnonRateThrottle):
cache = caches['alternate']
Custom Throttles
Implement custom throttles by overriding BaseThrottle:
import random
from rest_framework.throttling import BaseThrottle
class RandomRateThrottle(BaseThrottle):
def allow_request(self, request, view):
# Randomly throttle 1 in 10 requests
return random.randint(1, 10) != 1
With wait() Method:
class CustomThrottle(BaseThrottle):
def allow_request(self, request, view):
# Custom logic
if should_throttle:
return False
return True
def wait(self):
# Return seconds to wait
return 60
If wait() is implemented and request is throttled, a Retry-After header is included in the response.
Rate Throttle Internals
parse_rate()
Parses rate string into (num_requests, duration) tuple:
def parse_rate(self, rate):
"""
Given '60/min', returns (60, 60)
Given '1000/day', returns (1000, 86400)
"""
if rate is None:
return (None, None)
num, period = rate.split('/')
num_requests = int(num)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
return (num_requests, duration)
throttle_success() / throttle_failure()
Called when request passes or fails throttle check:
def throttle_success(self):
"""Insert current request timestamp into cache"""
self.history.insert(0, self.now)
self.cache.set(self.key, self.history, self.duration)
return True
def throttle_failure(self):
"""Called when request is throttled"""
return False
Settings
List of throttle classes to use by defaultREST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
]
}
Dictionary mapping scope names to rate stringsREST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day',
'burst': '60/min',
'sustained': '1000/day',
}
}
Number of application proxies in front of the API
Retry-After
Included when request is throttled and throttle implements wait() method:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Notes
Concurrency
Built-in throttle implementations are subject to race conditions under high concurrency. They may allow a few extra requests through due to non-atomic cache operations.
For strict rate limiting under concurrent load, implement a custom throttle class using atomic operations.
Throttle checks happen before the view executes. For high-traffic APIs, consider:
- Using a fast cache backend (Redis, Memcached)
- Configuring cache timeouts appropriately
- Monitoring cache hit rates
Multiple Throttles
You can use multiple throttles simultaneously:
class MyView(APIView):
throttle_classes = [
BurstRateThrottle,
SustainedRateThrottle,
AnonRateThrottle,
]
All throttle checks must pass for the request to be allowed.
Common Use Cases
Burst + Sustained Throttling
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'app.throttles.BurstRateThrottle',
'app.throttles.SustainedRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'burst': '60/min', # Max 60 per minute
'sustained': '1000/day' # Max 1000 per day
}
}
Different Rates for Anonymous vs Authenticated
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day', # Restrictive for anonymous
'user': '10000/day' # Generous for authenticated
}
}
Scoped Rates for Different API Sections
class UploadView(APIView):
throttle_classes = [ScopedRateThrottle]
throttle_scope = 'uploads'
class SearchView(APIView):
throttle_classes = [ScopedRateThrottle]
throttle_scope = 'search'
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'uploads': '10/hour', # Resource-intensive
'search': '1000/hour' # Less intensive
}
}