Why I hand-built my API docs instead of using Swagger UI
drf-spectacular gave me Swagger UI for free, but I built my own interactive API reference instead. How the custom docs page works — and why it was worth it.

Django REST Framework plus drf-spectacular hands you a full Swagger UI at a URL for almost no effort. For Push Relay's API I deleted it and hand-built the docs page instead. That sounds like a strange trade — throwing away working, auto-generated docs to maintain HTML by hand — so here's the reasoning, and how the page actually works.
Swagger UI is a tool bolted onto your product
Swagger UI is genuinely good at one thing: turning an OpenAPI schema into a browsable, try-it-out reference for almost no effort. But it always looks like Swagger UI — its own typography, layout and colours — and it reads as developer tooling parked next to the product rather than part of it. For a portfolio piece where the docs are meant to be part of the experience, that mismatch mattered.
A few smaller frictions pushed the same way:
- Its "Try it out" flow is fiddly — expand the operation, click into an editor, and set up auth in a separate dialog each session.
- It orders endpoints by tag, not by what you'd do first. My API's primary action is
POST /send; in Swagger it sat wherever the grouping put it. - It can't tell an onboarding story. A reference lists endpoints; it can't walk someone through how to wire the thing into their own app.
- It ships a large JavaScript bundle — and, via
drf-spectacular-sidecar, a pile of vendored static assets. One of those static paths is what 404'd on me in the first place.
What I wanted instead
I wanted the docs to feel like the rest of the site — same cobalt theme, same type — and to be genuinely quick to use: paste your key once at the top, then fire any endpoint against your own browser and see the real response. Copy-paste snippets in the languages people actually reach for, pre-filled with their key. And an opening "use it in your app" guide, because for a service the first question isn't "what are the endpoints" — it's "how do I adopt this."
How the custom page works
It's one Django template — no build step, no third-party scripts. The moving parts:
- Authorize once. A sticky bar takes your demo key and holds it in memory; every "Send request" button and every code snippet on the page picks it up. Clearing it flips the page back to the unauthenticated state.
- Try-it is a real request. Each endpoint has an editable JSON body; Send makes an actual
fetch()to the live API from your browser, then renders the status, timing and a syntax-highlighted response — no proxy, no mock. - Snippets are generated live. A small function turns the current endpoint, body and key into cURL, JavaScript, Python or Bash — so what you copy is exactly what you just ran, with your key already in it. They sit in a collapsed disclosure to stay secondary to actually trying the call.
- It knows your context. The label you generated on the Keys page is stashed in
localStorageand pre-filled into the example bodies, so the examples are about your subscription, not a placeholder. - A left rail with scroll-spy, task ordering (Send first), and an opening integration guide with the two adoption modes and the SDK snippets round it out.
I kept the schema, just not the UI
The part worth stressing: I didn't throw away OpenAPI. drf-spectacular still generates the machine-readable schema, still served at /api/v1/schema/ — so the contract and anything that consumes the spec still work. I replaced only the renderer. The schema stays the source of truth; the page is the human-facing layer on top.
The honest trade-off
Hand-writing the page means it can drift from the schema — add a field and I update the docs by hand, where Swagger would regenerate. That's a real cost. It's worth paying here because the API is small and stable (five endpoints) and the docs are a first-class part of the product. For a large or fast-moving API I'd take Swagger UI's zero-maintenance regeneration every time. The honest answer is that it depends on how much the surface changes and how much the docs matter as an experience — for this one, both pointed at building it myself.
You can try the page yourself — paste a key and fire a real notification at your own browser:
- The interactive docs
- Source on GitHub