Common Patterns¶
Public API + internal API with different limits¶
class PublicArticleViewSet(RateLimitHeadersMixin, viewsets.ReadOnlyModelViewSet):
throttle_classes = [rate_limit(rate="60/h", key="ip")]
class InternalArticleViewSet(RateLimitHeadersMixin, viewsets.ModelViewSet):
permission_classes = [IsInternalService]
throttle_classes = [rate_limit(rate="100000/h", key="api_key")]
Login/auth endpoints: strict per-IP limiting¶
Brute-force protection on authentication endpoints benefits from a tight, IP-scoped fixed or sliding window (no burst allowance, since burst tolerance is exactly what an attacker wants):
class LoginView(APIView):
throttle_classes = [
rate_limit(rate="5/m", algorithm="fixed_window", key="ip"),
]
Webhook receivers: generous limits, cost-weighted by payload size¶
class WebhookView(APIView):
throttle_classes = [
rate_limit(
rate="100000/h",
algorithm="token_bucket",
burst=5000,
key="api_key",
cost=lambda request: max(1, len(request.body) // 1024), # ~1 unit per KB
),
]
Read-heavy vs. write-heavy limits on the same viewset¶
class ArticleViewSet(RateLimitHeadersMixin, viewsets.ModelViewSet):
def get_throttles(self):
if self.request.method in ("GET", "HEAD"):
return [rate_limit(rate="1000/h", key="user")()]
return [rate_limit(rate="50/h", key="user", algorithm="fixed_window")()]
Global default + per-view override¶
# A shared default throttle used across most views:
DefaultThrottle = rate_limit(rate="1000/h", key="user")
class ArticleViewSet(RateLimitHeadersMixin, viewsets.ModelViewSet):
throttle_classes = [DefaultThrottle]
class ReportGenerationView(RateLimitHeadersMixin, APIView):
# A stricter override for one expensive endpoint:
throttle_classes = [rate_limit(rate="10/h", key="user")]
Multi-tenant SaaS: per-tenant AND per-user limits together¶
class ArticleViewSet(RateLimitHeadersMixin, viewsets.ModelViewSet):
throttle_classes = [
rate_limit(rate="10000/h", key="tenant", scope="tenant-budget"),
rate_limit(rate="500/h", key=combine("tenant", "user")),
]
A single heavy user can't exceed their own per-user limit, and no combination of users within one tenant can exceed that tenant's overall budget.
Monitoring rate-limit pressure¶
Log whenever RateLimit-Remaining drops below a threshold, to catch
clients approaching their limit before they start seeing 429s:
class LoggingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
remaining = response.headers.get("RateLimit-Remaining")
if remaining is not None and int(remaining) < 5:
logger.warning("Client %s nearing rate limit (%s left)", request.user, remaining)
return response