How do you keep API reference documentation in sync with an API that changes every sprint?
Keep API reference documentation in sync by generating schemas, paths and examples from an OpenAPI spec that changes with the handler, then validate that spec against real traffic in CI and hand-own the prose a spec cannot express. Use this api documentation answer to show the decision, trade-off, and evidence rather than a memorised definition.
What the interviewer is scoring
- Whether you can say why hand-written reference drifts as a structural fact rather than a discipline failure
- Does the candidate distinguish what a spec can express from what only prose can
- That they treat the spec itself as an artefact capable of lying, not as a guarantee
- Whether generation is tied to something enforcing in CI rather than to good intentions
- Whether the answer survives the case of a correct-looking page that omits a deprecation
Answer
Short answer
Keep API reference docs in sync by making the OpenAPI or schema definition part of the same pull request as the code, generating the mechanical reference from it, and checking real requests and responses against it in CI. Then explicitly own the human prose for workflows, errors and ordering, because generation only proves the page matches the spec, not that the spec teaches the integration.
Why hand-written reference drifts by construction
A hand-written reference page is a second copy of the interface, kept in a different file, edited by a different person, at a different time, under no pressure from a compiler or a test. Nothing in the system connects the two. When a field becomes optional, the code changes and the page does not, and the failure is invisible - the docs still build, the site still renders, and the page is simply wrong until a support ticket surfaces it. Drift is therefore not a symptom of a lazy team. It is the expected behaviour of two independent sources of truth, and the only reliable fix is to stop having two.
That is what generating a reference from a machine-readable description of the API buys you. Paths, methods, parameter names and types, request and response schemas, required-versus-optional, enum members, status codes: all of it becomes a build artefact derived from one source. If the derivation runs in CI and the site is rebuilt on merge, the mechanical half of the reference cannot lag the code by more than one deploy.
What a spec cannot carry
The parts of reference documentation that people actually get stuck on are the parts a schema has no field for. A schema can say status is one of three strings; it cannot say which transitions are legal or which of them is terminal. It can list a 409; it cannot say that a 409 here means a concurrent write and is safe to retry, whereas the 409 on the adjacent endpoint means a duplicate business key and retrying will never succeed. It cannot express that you must create a reservation before you capture it, that a token is scoped per tenant, or that the endpoint is cheap to call once and ruinous to call in a loop.
OpenAPI gives you description fields to put that prose in, which is the right place for it precisely because it travels with the definition rather than sitting in a separate page that can be deleted independently.
paths:
/v1/payments/{id}/capture:
post:
summary: Capture an authorised payment
description: >
Call only after /v1/payments has returned status=authorised.
Capturing twice with the same Idempotency-Key returns the original
result; without the header a second call is a second capture.
responses:
"409":
# The code alone is ambiguous - this line is the load-bearing part.
description: >
Payment is not in the authorised state. Not retryable; refetch
the payment and inspect status before deciding what to do.
Everything above the 409 could be generated. The two description blocks could not, and they are the reason a reader stops filing tickets.
Where the generation becomes the staleness risk
This is the half candidates skip. A generated reference feels authoritative, which makes its failures harder to notice than a stale hand-written page.
If the spec is a hand-maintained file sitting beside the code, you have not removed the second source of truth, you have renamed it. The spec drifts exactly as the page did, and now it drifts behind a facade of machine generation. If the spec is derived from annotations in the code, the annotations can be wrong in ways the compiler tolerates - a documented type that no longer matches the serialiser, an example left over from a rename. And a generated page is complete by construction, so its silence is easy to miss: an endpoint that has been soft-deprecated for two quarters still renders as a first-class entry, and an internal route that gained a public annotation by copy-paste appears on your public site.
The defences are all mechanical. Require the spec to change in the same pull request as the handler, so a reviewer sees the interface diff next to the implementation diff. Validate real request and response payloads against the spec in your integration tests, which catches the annotation that no longer reflects the serialiser. Lint the spec itself: fail the build when an operation has no description, when a documented example does not validate against its own schema, or when a response is declared with no explanation. And review the rendered diff, not only the source diff, because that is where an unintended endpoint becomes visible.
What the pipeline cannot rescue
None of this touches the prose that explains why the API is shaped as it is: the guide that walks a first integration end to end, the page on retry and idempotency policy, the migration note for a breaking version. Those have no generator and no test, so they need the one thing generation removes the need for - a named owner and a visible review date. Treating them as a small, explicitly owned set is honest. Pretending the pipeline covers them is how a technically current reference ends up useless.
Generation guarantees your reference matches the spec. Nothing except a test against real traffic guarantees the spec matches the service.
© 2026 Preptima. Originally published at preptima.com.
Likely follow-ups
- Where would you put the rule that a client must call the reservation endpoint before the capture endpoint?
- How do you stop an autogenerated page from silently gaining an endpoint nobody meant to make public?
- What does a spec linter buy you that a schema validator does not?
- How would you document an error that only appears under a specific upstream timeout?
Related questions
- Every architecture document you have inherited is out of date. How do you write documentation that survives contact with a changing system?mediumAlso on docs-as-code4 min
- A modal passed design and QA review, but keyboard users report they can tab out of it into the page behind, and once they do they cannot get back or close it. Diagnose it and tell me what a correct dialog does.hardSame kind of round: scenario4 min
- An asyncio call times out and you handle the TimeoutError, but the background task keeps running and mutates shared state a few seconds later. What happened, and how do you make the timeout actually stop the work?hardSame kind of round: concept4 min
- A transform has been writing wrong revenue figures for three days and six downstream tables have consumed it. How do you backfill the corrected data without double-counting anything?hardSame kind of round: scenario4 min