40 API Design Rules (FastAPI/Django/Flask)
Forty rules for Python web APIs that are consistent, secure, observable, and maintainable across FastAPI, Django REST Framework, and Flask.
Recipe
Apply during API design review before implementation. Map rules to OpenAPI schema and CI contract tests.
When to reach for this:
- Designing a new REST or RPC API
- Reviewing pull requests that add endpoints
- Standardizing APIs across microservices
API Rules Reference
| # | Area | Rules |
|---|---|---|
| 1-10 | URLs & versioning | Resource design |
| 11-20 | Request/response | Models & validation |
| 21-30 | Errors & security | Client experience |
| 31-40 | Ops & docs | Production readiness |
Deep Dive
URLs & Versioning (1-10)
- Plural nouns for resources -
/invoices, not/invoiceor/getInvoices. - Version in URL path -
/api/v1/invoicesfor breaking changes. - Nested resources max 2 levels -
/customers/{id}/invoices, not deeper nesting. - Use HTTP methods correctly - GET read, POST create, PUT/PATCH update, DELETE remove.
- POST for actions -
/invoices/{id}/sendfor non-CRUD operations. - Pagination on all lists -
?limit=50&cursor=abcor offset with max cap. - Filtering via query params -
?status=paid&since=2026-01-01, not new endpoints. - Consistent trailing slash policy - pick one; redirect the other (Django: configure
APPEND_SLASH). - Idempotency keys on POST -
Idempotency-Keyheader for payment and create operations. - HATEOAS optional - links in response when clients are diverse; skip for internal APIs.
Request/Response (11-20)
- Pydantic models for FastAPI - request and response schemas with examples.
- DRF Serializers or Pydantic for Django - validate input before business logic.
- ISO 8601 datetimes - UTC with
Zsuffix:2026-07-09T14:30:00Z. - Decimal for money - never
floatfor currency fields. - UUIDs for public IDs - opaque identifiers; auto-increment IDs internal only.
- Consistent envelope or bare objects - pick one style per API; document it.
- Field names in snake_case - JSON matches Python conventions unless external contract requires camelCase.
- OpenAPI spec generated - FastAPI auto; DRF with
drf-spectacular; Flask withflask-smorest. - Response model excludes internals - no ORM models returned directly.
- ETags for cacheable GETs -
If-None-Matchreturns 304 when unchanged.
Errors & Security (21-30)
- RFC 7807 Problem Details -
{"type", "title", "status", "detail", "instance"}. - Consistent error codes - machine-readable
codefield:INVOICE_NOT_FOUND. - 422 for validation errors - field-level detail array for client fixes.
- 401 vs 403 correctly - 401 unauthenticated, 403 authenticated but unauthorized.
- Rate limiting - per-IP and per-token;
429withRetry-Afterheader. - CORS explicit allowlist - never
*in production with credentials. - Auth via Bearer token or session - document scheme in OpenAPI
securitySchemes. - Input size limits - max body size, max upload, max query string length.
- No sensitive data in URLs - tokens and passwords in headers/body only.
- Audit log mutations - who changed what, when, on all POST/PATCH/DELETE.
Operations & Docs (31-40)
- Health and readiness endpoints -
/health(liveness),/ready(dependencies ok). - Structured request logging - method, path, status, duration_ms, request_id.
- Correlation ID propagation -
X-Request-IDin and out. - OpenAPI published -
/docsin dev; exported spec in CI artifact. - Contract tests - schemathesis or Dredd against OpenAPI in CI.
- Deprecation headers -
Deprecation: trueandSunset:RFC 8594 date. - Backward compatible additions - new optional fields ok; removing fields is breaking.
- Async routes for I/O - FastAPI
async defwith async DB drivers. - Background work off the request - Celery, ARQ, or FastAPI BackgroundTasks for email and exports.
- Load test before launch - locust or k6 against staging with production-like data volume.
Gotchas
- Returning ORM objects - lazy-load errors and data leaks. Fix: explicit response schemas.
- Float for currency -
0.1 + 0.2 != 0.3. Fix:Decimaleverywhere money touches JSON. - Inconsistent error shapes - clients cannot parse. Fix: one error schema enforced by middleware.
- No pagination on day one - breaking change when data grows. Fix: paginate from first release.
- OpenAPI drift from code - docs lie. Fix: generate spec from code; contract test in CI.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| GraphQL | Diverse clients, complex graphs | Simple CRUD microservice |
| gRPC | Internal service mesh | Public browser clients |
| Webhooks only | Event-driven integration | Request/response needed |
FAQs
FastAPI or Django for new API?
FastAPI for async microservices. Django for admin-heavy apps with ORM and auth built in.
snake_case or camelCase JSON?
snake_case default for Python APIs. camelCase only if frontend contract requires it (use alias).
How do I version?
URL path /v1/ for breaking changes. Additive changes within same version.
REST or RPC-style POST actions?
REST for resources. POST actions for operations that do not map to CRUD.
How do I document errors?
OpenAPI responses section per endpoint with example Problem Details.
Sync or async FastAPI?
async for I/O-bound with async drivers. sync def ok with thread pool for blocking ORMs.
How do I handle file uploads?
Multipart with size limits. Store in object storage; return URL, not file bytes in JSON.
API keys vs JWT?
JWT for user sessions. API keys for service-to-service. Document scopes for both.
How do I test APIs?
pytest + TestClient/httpx. Contract tests from OpenAPI. Integration tests on test DB.
Flask in 2026?
Valid for small services and extensions. FastAPI preferred for new async APIs.
Related
- 50 Python Rules - general rules
- 30 Security Rules - API security
- FastAPI Basics - FastAPI patterns
- Testing Web APIs - API testing
Stack versions: This page was written for Python 3.14.0, FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.