Django REST API Production Checklist
Review a Django REST API before production: authentication, permissions, throttling, pagination, validation, versioning, errors, CORS, docs, and observability.
Problem statement
An API has a different risk profile from a website. Clients are programs: they retry aggressively, send unexpected input, cache responses, and break when a field changes type. Mistakes are also less visible, because there is no page rendering incorrectly for someone to notice; instead, an integration quietly fails at 3 a.m. or a public endpoint leaks data no browser ever displayed.
This checklist reviews a Django REST Framework API before it is exposed to real clients. It assumes DRF, but the principles apply to any Django API, including Django Ninja or plain views returning JSON.
Quick answer
- Defaults: authentication required, JSON renderer only, pagination on, throttling on.
- Permissions: every endpoint's access rules are explicit and tested, including object-level checks.
- Input: validated by serializers with size limits; no mass assignment of sensitive fields.
- Output: only intended fields; no accidental exposure of internal or personal data.
- Errors: consistent JSON error responses for 4xx and 5xx, including outside DRF views.
- Compatibility: versioning strategy, additive changes, and a deprecation process.
- Operations: structured request logs, metrics per route, rate limits at the proxy, and documentation that matches reality.
Checklist
1. Safe framework defaults
REST_FRAMEWORK = {
"DEFAULT_RENDERER_CLASSES": ["rest_framework.renderers.JSONRenderer"],
"DEFAULT_PARSER_CLASSES": ["rest_framework.parsers.JSONParser"],
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 50,
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {"anon": "30/minute", "user": "600/minute"},
"NUM_PROXIES": 1,
}
What to check:
- the browsable API renderer is disabled in production
- default permissions are restrictive, so a view missing explicit permissions is closed
- pagination applies to every list endpoint, with a bounded
max_page_size - throttle rates are backed by a shared cache (Redis), not local memory
2. Authentication
What to check:
- the authentication scheme fits the clients: session authentication for same-site browser apps, tokens or JWT for machines and mobile apps
- tokens are revocable; DRF's default
TokenAuthenticationtokens do not expire on their own, so add rotation or expiry if long-lived credentials are a concern - JWT settings (if used) have short access token lifetimes, a refresh mechanism, and a way to invalidate compromised tokens
- credentials travel only over HTTPS, and plain HTTP is rejected at the proxy rather than redirected
WSGIPassAuthorization On(Apache) or equivalent, so theAuthorizationheader reaches Django- login and token endpoints are throttled more strictly than normal endpoints
3. Permissions and object-level access
What to check:
- every view sets
permission_classesintentionally, even when the default is restrictive - object-level permissions are enforced for detail endpoints;
get_queryset()filters by ownership rather than relying on unguessable IDs:
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Order.objects.filter(customer=self.request.user.customer)
- write endpoints check the same ownership as read endpoints
- tests cover "another user's object returns 404 or 403" for each resource
- staff-only actions are not reachable by regular tokens
4. Input validation
What to check:
- serializers define explicit fields; avoid
fields = "__all__"for writable serializers, which can expose or accept fields you did not intend - read-only fields (
id,created_at,owner,statustransitions) are markedread_only - numeric and string limits (
max_length,min_value,max_value) are set - nested writes are controlled, not accidental
- file uploads validate type and size, and are stored outside the web root
- request size limits configured (
DATA_UPLOAD_MAX_MEMORY_SIZE,DATA_UPLOAD_MAX_NUMBER_FIELDS, proxyclient_max_body_size) - query parameters used for filtering and ordering are restricted to a known list, so clients cannot order by arbitrary columns or filter on sensitive fields
5. Output control
What to check:
- serializers expose only intended fields; no password hashes, internal notes, tokens, or other users' personal data
- error messages do not leak implementation details (SQL, file paths, stack traces)
- list endpoints do not include heavy nested structures by default; provide expansion parameters instead
- consistent field naming and types across endpoints, since clients depend on them
Review the actual payloads:
curl -s -H "Authorization: Token $TOKEN" https://api.example.com/api/orders/ | python3 -m json.tool | head -40
6. Error responses
What to check:
- DRF returns JSON for handled exceptions; unmatched URLs and unhandled errors also return JSON through
handler404andhandler500 - validation errors have a consistent shape clients can parse
429responses includeRetry-After500responses are generic in content but logged in full internally- proxy-generated errors (
413,502,504) are documented for clients, ideally with a JSON body
7. Performance
What to check:
- list endpoints use
select_relatedandprefetch_relatedto avoid N+1 queries - serializer method fields do not issue per-object queries
- expensive aggregates use annotations
- pagination limits are enforced server-side
- caching for read-heavy public endpoints where appropriate, with correct
Varyheaders - query counts asserted in tests for key endpoints
8. Rate limiting and abuse protection
What to check:
- DRF throttles for per-user and per-endpoint limits
- proxy-level rate limiting for cheap protection against floods
NUM_PROXIESor equivalent configured so throttling identifies clients correctly behind proxies- limits documented for API consumers, with headers or documentation explaining them
- expensive endpoints (search, exports, report generation) have stricter limits or run asynchronously
9. CORS and browser clients
What to check:
CORS_ALLOWED_ORIGINSlists exact origins; no wildcard with credentialsCorsMiddlewareis placed above middleware that can return responses early- preflight requests succeed for the methods and headers clients use
CSRF_TRUSTED_ORIGINSset for session-authenticated browser clients
10. Versioning and change management
What to check:
- a versioning approach is decided (URL path such as
/api/v1/, headers, or none for internal APIs) - additive changes only within a version; breaking changes create a new version
- deprecation process: announce, add
DeprecationorSunsetheaders, monitor usage of old endpoints, remove after a defined period - clients can discover the current version and changelog
11. Documentation
What to check:
- an OpenAPI schema is generated (for example with drf-spectacular) and matches the implementation
- schema and interactive docs are appropriately exposed: public for public APIs, authenticated for internal ones
- examples show authentication, pagination, filtering, and error responses
- documentation is regenerated in CI so it cannot drift
12. Observability
What to check:
- structured request logs with method, route pattern, status, duration, and client identity (not raw paths with IDs)
- metrics per route: request rate, error rate, latency percentiles, and throttle rejections
- errors reported to an error tracker with request context
- alerts on 5xx rate, sustained 429 rate, and latency regressions
- a health endpoint for load balancers that does not require authentication or throttling
13. Deployment settings
What to check:
DEBUG = False, correctALLOWED_HOSTS, HTTPS enforced, HSTS enabledSECURE_PROXY_SSL_HEADERset when behind a TLS-terminating proxy- Gunicorn worker and timeout settings suited to API traffic (short timeouts, threads for I/O-bound work)
- background processing for anything slow, returning
202 Acceptedwith a status URL
Verify
# Unauthenticated access is rejected
curl -s -o /dev/null -w "%{http_code}\n" https://api.example.com/api/orders/
# Another user's object is not accessible
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Token $OTHER_TOKEN" https://api.example.com/api/orders/1042/
# Pagination and throttling
curl -s -H "Authorization: Token $TOKEN" "https://api.example.com/api/orders/?page_size=10000" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d['results']), d['count'])"
for i in $(seq 1 40); do curl -s -o /dev/null -w "%{http_code} " https://api.example.com/api/public/status/; done; echo
Verification check:
- unauthenticated and cross-tenant requests are rejected
page_sizecannot exceed the configured maximum- throttling returns
429withRetry-After - unknown routes return JSON
404 - documented schema matches actual responses
Explanation
APIs expose data and behavior directly, with clients that do not forgive inconsistency. The safest defaults are restrictive (authentication required, JSON only, paginated, throttled), with each endpoint opening exactly what it needs. Explicit input and output control prevents both injection-style problems and accidental data exposure, while versioning and observability make the API maintainable once real clients depend on it.
Edge cases / notes
Public read-only APIs
Even without authentication, apply pagination, throttling, caching, and careful field selection; public endpoints attract scrapers.
Webhooks you receive
Incoming webhooks need signature verification, replay protection, and idempotent processing, and should be excluded from browser-oriented protections such as CSRF while remaining authenticated by signature.
Mobile clients
Old app versions stay in use for a long time. Plan for long support windows on API versions, and use feature flags to change behavior without forcing upgrades.
Common mistakes
- Leaving the browsable API and
AllowAnydefaults in production. - Relying on unguessable IDs instead of queryset filtering for authorization.
fields = "__all__"on writable serializers.- No pagination limit, allowing a single request to fetch an entire table.
Next steps
- Configure the stack with Deploy a Django REST Framework API to Production.
- Fix browser access with Fix CORS Errors with a Django API in Production.
- Add proxy protection with Rate Limit Django Endpoints with Nginx.
- Keep endpoints fast with Find and Fix N+1 Queries in Django.
FAQ
What are safe DRF defaults for production?
JSON renderer only, explicit authentication classes, IsAuthenticated as the default permission, pagination with a maximum page size, and throttling backed by a shared cache.
How should a Django API handle authorization for detail endpoints?
Filter the queryset by ownership in get_queryset() and enforce object-level permissions, so another user's object returns 404 or 403 rather than data.
Does a Django API need CSRF protection?
Session-authenticated browser requests do; token-authenticated requests from non-browser clients do not. DRF's SessionAuthentication enforces CSRF for unsafe methods.
How do I version a Django REST API?
Choose one scheme, commonly a URL prefix such as /api/v1/, keep changes additive within a version, and introduce breaking changes in a new version with a documented deprecation period.