OpenAPI, schema generation, and request/response schemas
coreintermediateOpenAPI is a machine-readable description of an HTTP API — every path, every method, the shape of each request body, the shape of each response, and the authentication each endpoint expects — written as a single JSON or YAML document. Because it is machine-readable, one file drives several things at once: interactive documentation, generated client libraries in a dozen languages, request validation in a gateway, and contract tests. In DRF you do not write it by hand. A generator walks your URLconf, reads each view's serializer, and produces the document — which is why the schema is only as accurate as the serializers and the type hints it was derived from.
Think of it as
The reason to generate rather than write is that a hand-maintained document describes the API you *meant* to build, and it starts drifting on the first merge. A generated one describes the API you actually shipped, and its errors are visible: a missing response type in the document means a missing declaration in the code, so fixing the document fixes the code too. That makes generation a correctness tool rather than a documentation chore. What it cannot do is invent information you never gave it. A generator reads serializers well and views poorly, because a view's behaviour lives in Python control flow that no static pass can summarise — so a hand-written `Response({...})`, a dynamic `get_serializer_class()`, or an error shape produced by an exception handler are all invisible unless you annotate them. That is the actual work in this area: not running the generator, but noticing where it had to guess. And once the document exists, treat it as a build artifact rather than a page — check it into the repository, diff it in CI, and a breaking change becomes a reviewable line in a pull request instead of something a client discovers.
What we're doing: Generate a schema that describes the endpoints accurately — including the custom action and the error shapes the generator cannot see.
- 1
- One setting switches the whole project onto the generator. Every existing view is described from its serializer without further work.
- 5
- Excluding the schema endpoint from its own document — otherwise generated clients acquire a method for downloading their own definition.
- 9–12
- Declaring the error envelope as a serializer lets every endpoint reference the same schema component, so the contract is stated once and reused.
- 16
- The only line needed for standard CRUD: `serializer_class` supplies both the request and the response schema for all five actions.
- 19–23
- The annotation the generator genuinely cannot infer. Without it the action appears with no request body and no documented responses at all.
Why this works: Everything derivable is derived, and the two things that are not — the custom action and the error shapes — are declared once next to the code they describe, so they move when it does.
Maintaining API documentation as a separate hand-written page
Wrong
Better
What you see: A client integrates against the documented shape and gets a 400 for a field the docs say is required and the serializer says is read-only. Nobody can say when the two diverged, because nothing ever compared them.
Why: A hand-written page has no mechanism that forces it to change when the code does, so its accuracy decays silently from the first merge onward. A generated schema derives from the same serializers that serve the requests, so it cannot describe a shape the API does not have — and committing it makes any change to that shape a visible diff someone has to approve.
- 1 · The generator walks the URLconf — Every routed view becomes a path and a set of operations. Nothing here is written by hand, so nothing here can drift.
- 2 · Serializers become schemas — Field types, required-ness, choices, and read-only flags all transfer. A vague serializer produces a vague schema.
- 3 · You annotate what it cannot infer — Custom actions, error shapes, and dynamic serializer selection are Python control flow — invisible to a static pass until declared.
- 4 · One document, four consumers — Swagger UI and ReDoc render it, client generators compile it, a gateway validates against it, and CI diffs it to catch breaking changes.
What the generator reads, and what it cannot see
Together
Remember: OpenAPI is a machine-readable description of the API, and in DRF it is generated from your URLconf and serializers rather than written. Generation makes it a correctness tool: it describes what you shipped, and a gap in the document is a gap in the code. But it can only read what is declarative — custom `@action` responses, dynamic serializer selection, and every error shape are invisible until annotated with `@extend_schema`. Commit the generated file and diff it in CI, so a breaking change becomes a line someone has to approve.
See also: swagger ui redoc auth and examples · drf spectacular and drf yasg · versioning backward compatibility and deprecation · custom pagination and page size limits

