River
Y CombinatorBacked by Y Combinator
FREE TEMPLATE

API Documentation and Reference Template

Seven documents and three sheets, including the error reference keyed on problem type and a coverage check that says which of your examples actually validate.

Free download  ·  No account needed

Your generator already produced a reference page. Every endpoint is on it, every parameter has a type, and the build is green. The gap is what the specification never said, and it is permitted in writing: OpenAPI requires the Responses Object to carry one response code, and says that where only one is provided it should be the successful one. An operation returning five distinct failures while declaring a single 201 is a conforming document, so no linter has grounds to complain and no generator can invent the rest.

The second gap is that an example is never checked against the schema printed beside it. In JSON Schema 2020-12 the examples keyword sits in the vocabulary for basic meta-data annotations, and validity against the associated schema is recommended rather than required. So a request example sending a renamed field is a valid document, nothing errors, and it ships. An example wrong for eleven months is the version of your API that your integrators actually built against.

So this pack ships two registers a generated page cannot produce. One counts declared failures separately from declared responses and never credits a bare default. The other carries present, validates and runs as three columns, plus which example wins when two collide. Alongside them, the error reference is keyed on problem type, because one status code is not one error. For code you did not write, start from the inherited codebase documentation instead.

The operation that declares no failure, and the example nobody validated

Every state in these three sheets is one a conforming OpenAPI document is allowed to be in. None of them fails a linter.

Endpoint Register

Illustrative rows for a fictional parcel shipping API, Waypost.

OperationStatusResponses declaredErrors declaredExamples presentExamples validatingOwner
POST /v1/ratespublished200, 400, 422, 4293 of 42 of 22 of 2Priya N
POST /v1/shipmentspublished201 only0 of 11 of 10 of 1Tomas K
GET /v1/shipments/{id}published200, 404, default1 of 2, plus a bare default1 of 11 of 1Tomas K
DELETE /v1/shipments/{id}partial204, 409, 4222 of 30 of 0n/a, none presentTomas K
GET /v1/tracking/{number}partial200, 404, 4292 of 31 of 10 of 1Ada W
POST /v1/labelsundocumentednot in the specificationunknown0 of 0n/aunowned
POST /v1/webhooksdeprecated201, 400, 4092 of 31 of 11 of 1Ada W
GET /internal/v1/rate-cardsinternal, published by mistake200, 4031 of 21 of 11 of 1Priya N

Row two is a conforming specification. The Responses Object needs one response code and the guidance is that a lone code should be the successful one, so a document declaring only a 201 passes every linter. That operation returns five distinct failures in production and the generated reference page shows one green row.

A bare default is never credited as an error. Row three declares a default with a description and no schema, which is exactly what the specification permits. It renders as a row telling a reader that failures exist and nothing about their shape, and every 5xx lands there undifferentiated.

Row six is in the router and not in the document. No generator can report it, because a tool cannot see what is missing from its own input.

Example Coverage Check

Present, validates and runs are three separate columns. Nothing in a build checks the second.

OperationKindLives inPresentValidatesFirst failureOverridden byRuns
POST /v1/ratesrequestmedia typeyesyesnonenothingyes
GET /v1/tracking/{number}responsemedia typeyesnoevents is array<object> in the schema, array<string> in the examplenothingno
POST /v1/addresses/validaterequestschema objectyesyesnonethe media-type examplenever rendered
POST /v1/addresses/validaterequestmedia typeyesnosends postcode, schema requires postal_codenothing, this one winsno
POST /v1/shipmentsresponsemedia typeyesnorate_id required in the schema, absent from the examplenothingno
DELETE /v1/shipments/{id}responsenowherenon/an/an/an/a
GET /v1/shipments/{id}response, defaultnowherenon/an/an/an/a
POST /v1/webhooksrequestmedia typeyesyesnonenothingno, returns 409

The tracking example has been wrong for eleven months. In JSON Schema 2020-12 the examples keyword sits in the vocabulary for basic meta-data annotations, and validity against the associated schema is recommended rather than required. OpenAPI says an example should match its schema. Nothing errors, so no build broke.

Rows three and four are the same field, twice. A media-type example overrides a schema-level one, and the schema-level field is deprecated in the current specification version. The correct example is in the document and no reader can reach it; the stale one predates a field rename and is the one that renders.

The last row validates cleanly and returns a 409 when sent, because a schema cannot know that a webhook URL is already registered.

Problem Type Register

One row per type URI, not per status code. Two of these share a 409.

TypeStatusExtension membersWhat to doURI resolvesIn spec
rate-expired409rate_id, quoted_at, expires_atRe-quote, retry once with the new rate_idyesno
label-already-generated409shipment_id, label_id, generated_atStop retrying. Fetch the existing labelyesno
address-unverifiable422field, suggested_value, confidenceShow the suggestion to the user. Never substitute ityesyes
insufficient-postage-balance402balance, required, top_up_urlTop up, then retry the same requestno, 404sno
carrier-unavailable503carrier, retry_after_secondsRetry after the delay, or re-quote excluding that carriernolands in the bare default
carrier-account-suspended403carrier, suspended_at, contactStop. Contact the carrier. Retrying never worksyesno
about:blank401, 404noneRefresh the token, or stop. Deliberateregistered by the standard404 only
(no type member sent)429none, plain JSON bodyHonour Retry-After. The window is fixedn/ayes

The first two rows are why a status-code table is the wrong artifact. Both return 409. Retrying the first usually succeeds and retrying the second never does, so code branching on the number produces a retry loop against an operation that buys postage. RFC 9457 requires consumers to use the type URI as the primary identifier and makes the status member advisory.

Row four returns a type URI that 404s. The standard says dereferencing a locator type URI should provide human-readable documentation for the problem type, so this is a promise made on the API’s behalf and broken. It is also the cheapest row here to fix.

The last row sends no type member, so its type is assumed to be about:blank, which claims the status code carries the whole meaning. On a 429 with a retry window, it does not.

What's in the pack

01

Endpoint Register

One row per operation, counting declared responses and declared failures separately, never crediting a bare default, with four states including the operations missing from the spec entirely.

02

Example Coverage Check

One row per example, carrying present, validates and runs as three columns, the first schema failure on each, and which example wins when two collide.

03

Problem Type Register

One row per type URI with its extension members, the cause, whether the URI resolves, and what the integrator should actually do about it.

04

Error Reference

The document each locator type URI resolves to, with two failures on one status code written up separately because the correct response to each is the opposite.

05

Reading a Specification

Every claim above quoted from the primary source, so the register survives the first person who asks why their linter never mentioned any of this.

06

Getting Started and Authentication Guide

A three-call quickstart with the real failure at each step, plus scopes, idempotency, key rotation and webhook signature verification.

07

Changelog

Where a deprecation date, its replacement and its migration live, since the specification's deprecated field is a boolean with nowhere to put any of the three.

How to use it

  1. 1

    Open in River, or take it blank

    Send the pack your OpenAPI or Swagger document inside River, or download the seven documents and three sheets and work through them without an account.

  2. 2

    Count before you judge

    Declared responses and declared failures land as separate columns, and a bare default is named rather than counted as error documentation.

  3. 3

    Check every example

    Each one is validated against the schema beside it, then sent, because a passing schema check and a working request are different questions.

  4. 4

    Key the errors on type

    Problem types come from what production returns rather than what the document declares, and each locator URI gets the page the standard says it owes.

Frequently asked questions

Is this template free?

Yes, and the download needs no account and no card. Edit with AI is the optional half: it reads the specification you send, fills the endpoint register, and checks every example against the schema printed beside it. Every other pack is in the template library.

What format are the downloaded files?

Seven documents as Word files and three sheets as CSVs, in one zip. The sheets open in Excel, Numbers or Google Sheets with the columns already ordered, and the documents open in Word or Pages. Nothing needs converting to be readable.

We already generate docs from OpenAPI. What does this add?

The two things your generator has no input for. It cannot show a failure your document never declared, and it cannot tell you that a published example contradicts the schema above it. Both are permitted by specification, so nothing in your pipeline reports either one.

Why not just list our error codes in a table?

Because one status code is not one error. RFC 9457 requires consumers to use the type URI as a problem type's primary identifier and makes the status member advisory. Two failures on 409 where retrying fixes one and causes the other cannot share a row without producing a retry loop.

How does it find endpoints that are not in our spec?

By comparing the document against what actually serves traffic: your route definitions, your gateway config, and the paths in your access logs. No generator can do this, because a tool cannot report what is absent from its own input, so an endpoint that shipped behind a flag stays invisible.

Our examples look fine. Are they?

Nothing has checked. Both specifications say an example should match its schema and neither says must, so a mismatch is a conforming document. The coverage sheet reports the first failure on each, then sends it, since an example can validate cleanly and still return a 409.

What else should we document alongside this?

The data layer and the release trail. The database schema documentation covers the tables behind these endpoints, release notes from commits turns a merge history into what changed for a user, and the data pipeline pack covers the jobs feeding them. Once an operation here is on its way out, the API deprecation policy pack works out who still calls it.

Find out what your specification never said

Take the Word documents and CSV sheets blank, or open this exact pack in River and let it read the specification you already publish.

Edit with AI