Documentation & DX / Tutorial
Designing an API Reference Developers Actually Read: Examples, Errors, and Auth Up Front
An API reference is read under pressure, mid-debug. Design it that way: auth before endpoints, a worked example per operation, and errors documented like features.

You will finish this tutorial able to restructure an API reference around how it is actually used: opened mid-task, under time pressure, by a developer who wants one of three things — how to authenticate, a request they can copy, or the meaning of the error they just received. Most references are designed instead as inventories of endpoints, generated from a spec and published with pride. The spec is the floor. Everything in this tutorial is what you build on top of it.
Start from the reading situations, not the endpoints
Nobody browses an API reference. Readers arrive from a search engine, a stack trace, or a colleague’s link, and they land deep inside it — rarely on the page you consider the beginning. That single fact drives every design decision that follows. Each endpoint page must stand alone: it cannot assume the reader has seen your introduction, your conventions page, or the previous endpoint in the list. Design for the landing, not the journey.
It also means your reference serves three distinct reading situations, and you should be able to point at where each is served:
- First contact: “Can this API do what I need, and how do I get a key?”
- Integration: “Give me the exact request shape, with a realistic example.”
- Debugging: “It returned this error. What does it mean and what do I change?”
A reference that serves only the second situation — the most common outcome of spec generation — is a reference that abandons readers at the two moments of highest emotion: the hopeful start and the frustrated middle.
Auth up front, on every surface
Authentication is the gate every single reader must pass, so it gets the placement of a headline act: before the endpoint catalogue, in the reference’s own opening pages, and summarised again wherever a first request is shown. Not linked to a separate guide. Not implied by a lock icon. Written out: how to obtain credentials, how to send them, what a correctly authenticated request looks like, and what the failure looks like when you get it wrong.
That last element is the one teams skip, and it is the highest-value paragraph in your entire reference. The first error almost every integrator meets is an authentication error. If your docs show them the exact response body of an unauthenticated request, labelled “if you see this, your header is missing or malformed,” you have converted the most common support ticket in API-land into a self-service moment.
An illustrative authentication summary, the shape your version should take:
GET /v1/charges HTTP/1.1
Host: api.example.com
Authorization: Bearer <your_api_key>
→ 401 Unauthorized
{
"error": {
"code": "authentication_required",
"message": "No API key provided in the Authorization header."
}
}One worked example per operation, minimum
The request and response schema tells a developer what is possible. An example tells them what is normal. Both are required, and the example is the one readers copy — which makes it the highest-leverage text on the page and the part most often generated by lorem-ipsum thinking. A worked example earns its place when it meets four bars:
- Realistic values.
cust_8f3ka92teaches more thanstring; a plausible email teaches more thanuser@example.comrepeated forty times. - The interesting fields populated. An example where every optional field is empty demonstrates the minimum. Include the fields integrators most commonly need, so the copied request survives contact with their real use case.
- A response that matches. The response example must be what that request actually returns, including the boring fields — IDs, timestamps, status values — because developers write parsers against your example before they read your schema.
- Errors counted as examples. If an operation has a failure mode integrators hit regularly, it gets a worked example too, not a prose mention.
Keep examples synchronised with reality the same way you keep code honest: generate them from the same source as the reference where you can, and where you cannot, treat a drifted example as a bug with the same severity as a wrong parameter type. Developers trust examples over prose every time; when the two disagree, you have not written documentation, you have written a trap.
Errors documented like features
Here is the asymmetry at the heart of reference design: teams document the happy path in exhaustive detail and the error path as an afterthought — a table of status codes copied from a framework default. But debugging is where reference reading peaks. A developer with a working integration does not visit your docs; a developer staring at a 422 does, immediately, with feeling.
Document errors to the standard of the rest of the reference:
| Error documentation element | Weak version | Version that gets read |
|---|---|---|
| Error codes | HTTP status list only | Stable machine-readable codes, one per failure cause |
| Messages | The raw API message quoted back | What the error means in the integrator’s terms |
| Remediation | “Check your request” | The specific change that resolves it, per code |
| Examples | None | A real failing request and its real response body |
| Rate limits and retries | A line in a FAQ | Documented on the error itself: limit, window, retry guidance |
Two principles hide inside that table. First, stable error codes are a documentation feature: an integrator can search a code, bookmark it, and build handling around it, while a bare status code forces them to parse prose under pressure. If your API does not emit stable codes, that is an API design bug your docs are merely revealing — say so internally, loudly. Second, remediation is the content. The meaning of an error can usually be inferred; the fix often cannot. Every error entry that ends without “change this” has failed at its only job.
The anatomy of an endpoint page that works
Assemble the pieces in the order a debugging or integrating reader scans:
- Purpose in one sentence — what this operation does, in the integrator’s vocabulary, before any parameters.
- The request at a glance — method, path, and the worked example, visible without scrolling through the schema.
- Parameters with judgement — required and optional clearly separated, defaults stated, and constraints (lengths, formats, allowed values) written as facts, not warnings.
- The response example, followed by field descriptions for anything non-obvious.
- This operation’s errors — the specific codes it can return, each with meaning and fix, not a link to a global error page alone.
- Idempotency, pagination, and rate limits where they apply to this operation — the operational behaviours integrators discover painfully when docs omit them.
Conventions: the pages that save a thousand explanations
Alongside auth, your reference needs a short conventions layer — the behaviours that apply across every endpoint, documented once, in the reference itself: how pagination works and what its cursors mean, how filtering and sorting are expressed, what idempotency keys do and how long they are honoured, how rate limits are communicated in response headers. These are the questions every integrator asks exactly once, usually by experiment, at the cost of an afternoon.
The design error to avoid is scattering these behaviours across endpoint pages, restated slightly differently each time, until they drift into contradiction. The opposite error — one conventions page that endpoint pages never mention — fails differently: the integrator debugging pagination at midnight is on an endpoint page and will not go hunting for a global essay. The pattern that works is hub and pointer: conventions documented once, authoritatively, and every endpoint page that exhibits the behaviour carries one sentence naming it and linking to the hub. “Results are paginated with cursor-based navigation” plus a link costs the reader nothing and saves them the experiment.
Versioning deserves the same treatment in miniature. Your reference should state, on its conventions page and in its introduction, how the API versions, what a version change can break, and how long deprecated behaviour is supported — in plain operational language. Integrators plan around these answers. A reference that stays silent on them is not being neutral; it is exporting the planning risk to every team that builds on you, and they notice.
What generated references cannot give you
Most references today are generated from an OpenAPI or similar specification, and generation is genuinely good at inventory: every path, every parameter, every schema, never out of date by omission. Honour that strength and be clear-eyed about the boundary. A generator cannot write the sentence that tells a reader when to use this endpoint instead of its neighbour. It cannot choose the realistic example over the syntactically valid one. It cannot decide that this error, of the fourteen the schema permits, is the one integrators actually hit and deserves a worked example. It cannot write your auth narrative, because authentication is a story about your product’s trust model, not a schema.
So treat the spec as a contract with the generator: the spec owns completeness and accuracy of structure, and your team owns everything a reader feels. Descriptions in the spec deserve real writing effort, because they are the one prose surface that appears everywhere the spec is rendered — reference, SDKs, client generators. And the hand-written layer — introduction, auth, conventions, error guidance — should be reviewed with the same seriousness as endpoint changes, because from the reader’s side of the screen, it is the reference; the generated inventory is just the part they search within.
Verdict
A reference developers actually read is not more complete; it is more considerate. It puts authentication where the first question is, treats every example as code that will be copied and trusted, and documents errors with the seriousness of features because errors are where its readers live. Generate the inventory from your spec by all means — then go back and do the human work the generator cannot: the judgement, the realistic examples, and the fixes. That layer is the difference between a reference that exists and a reference that gets used.


