~/TechPurAI
~/tutorials/django-rest-framework/documenting-with-openapi
intermediate·part 21 of 22·2 min read

Documenting the API with drf-spectacular and OpenAPI

Updated Aug 16, 2026Python · Django

The browsable API (part 18) documents this project informally, one endpoint at a time, by actually being it. OpenAPI is a formal, machine-readable specification of the same information — every endpoint, every field, every possible response — that other tools (client code generators, API testing tools, a proper docs site) can consume directly.

Installing drf-spectacular

bash
pip install drf-spectacular
python
# taskapi/settings.py
INSTALLED_APPS = [
    # ...
    "drf_spectacular",
]

REST_FRAMEWORK = {
    # ... existing settings ...
    "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}

SPECTACULAR_SETTINGS = {
    "TITLE": "Task API",
    "DESCRIPTION": "A task management API built across the DRF from scratch series.",
    "VERSION": "1.0.0",
}

drf-spectacular is the current standard schema generator for DRF — it introspects TaskSerializer's fields, TaskViewSet's actions, and every permission and pagination setting already configured, and builds an OpenAPI schema from what's already there. Nothing about the API itself changes; this is purely additive.

Serving the schema and interactive docs

python
# taskapi/urls.py
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

urlpatterns = router.urls + [
    path("schema/", SpectacularAPIView.as_view(), name="schema"),
    path("docs/", SpectacularSwaggerView.as_view(url_name="schema"), name="docs"),
]

/api/schema/ serves the raw OpenAPI document (YAML by default) — the machine-readable file other tools consume. /api/docs/ serves Swagger UI, an interactive page built from that same schema: every endpoint, every field, with a "Try it out" button that sends real requests, generated entirely from code already written in parts 1 through 20.

What it picks up automatically

Adding detail the schema can't infer

python
from drf_spectacular.utils import extend_schema

class TaskViewSet(ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer

    @extend_schema(description="Marks a task as completed. Idempotent — calling it twice is safe.")
    @action(detail=True, methods=["post"])
    def mark_complete(self, request, pk=None):
        # ... unchanged from part 9 ...

@extend_schema adds documentation the code itself can't express — why an endpoint exists, or a behavioral guarantee like idempotency, isn't something a schema generator can infer purely from type signatures.

Why generated docs beat hand-written ones

A separate, hand-written API reference document drifts out of sync with the actual code the moment anyone changes a serializer field or forgets to update it — a familiar failure mode from any project with docs maintained separately from the implementation. A generated schema can't drift the same way, since it's read directly from the same TaskSerializer and TaskViewSet classes serving real requests.

Last part: getting this from runserver on a laptop to a real, deployed API.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous20. API versioning: URL and header-based strategiesnext →22. Deploying a DRF API to production