Toni MaxxToni Maxx

Notes

Documenting a REST API With OpenAPI

A reference page that lists endpoints is not enough. A developer who integrates your API also needs to know what happens when a call fails, how to retry safely, and how to trust a webhook. I wrote a fictional API to show this: the Sample Orders API, a small bakery shop with products, customers and orders. There is no running service behind it and all data is made up. Rate limits, token lifetimes and the retirement notice are illustrative numbers, not commitments.

Start with the contract

Every call follows the same rules, stated once at the top of the page.

  • One versioned base URL, HTTPS only. Plain HTTP is refused, so a key is never sent in clear text by mistake.
  • JSON in and out. Timestamps are UTC. Money is an integer in the smallest unit, such as total_cents, plus a currency code. No floating-point prices, so totals add up exactly.
  • Ids are opaque strings with a type prefix. Clients should not parse them.
  • Credentials travel in headers only. The API rejects a key in the query string, since URLs end up in logs and history.

Errors a client can use

The API uses one error format, problem details, with the content type application/problem+json. Every error has the same fields: type, title, status, detail, instance and a request_id. Validation errors add a list of fields.

The type is the stable part. Clients branch on it, not on the text. The detail is for people and may change wording. The page lists every problem type with its status and when it appears, for example a 409 out-of-stock or a 422 validation-failed. A table like that saves a support email.

Retries that cannot double-charge

A timeout leaves the client unsure whether an order exists. So writes accept an Idempotency-Key header. The server keeps the first result for 24 hours and replays it on a retry. The page spells out the four cases:

  • Same key and body after the first finished: the stored response comes back, marked as a replay.
  • Same key and body while the first is running: a 409 with a Retry-After.
  • Same key, different body: a 422, because that is a client bug.
  • A new key: a new order.

For rate limits, the API sends RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset on every response, and Retry-After on a 429. The advice to clients is to wait, add random jitter, and back off for server errors.

Signed webhooks

Instead of polling, a client registers an HTTPS endpoint and receives events such as order.paid. Anyone can post to a public URL, so each delivery carries a signature header with a timestamp and an HMAC-SHA256 value. The receiver:

  1. Reads the raw body, not re-serialised JSON.
  2. Computes the HMAC over the timestamp and the body with its signing secret.
  3. Compares in constant time.
  4. Rejects timestamps more than five minutes from its own clock, which stops replays.
  5. Stores processed event ids, because delivery is at least once.

The page includes verification code in several languages. I ran the Node version against the test vector on the page.

Pagination

List endpoints use cursors with a fixed envelope: a data array and a page object with next_cursor and has_more. A cursor stays correct while rows are added or removed, which offset pagination does not guarantee. Results are newest first, limit runs from 1 to 100, and the client passes the cursor back unchanged.

Versioning

The major version is in the path. Within a version, new endpoints, optional parameters and response fields are allowed, and clients must ignore fields they do not know. Removing or renaming a field, or tightening validation, ships as a new major version that runs beside the old one. When a version is retired, responses carry Deprecation and Sunset headers during the notice period.

What the OpenAPI file gives a reader

The same contract exists as one file, openapi.json: OpenAPI 3.1, 9 paths, 16 operations, 15 schemas, three security schemes and the webhook delivery. A reader can:

  • import it into a tool to browse every endpoint;
  • generate a client in their language;
  • build contract tests that check responses against the schemas.

I checked the file with a small structural script and an OpenAPI linter before publishing. It is written by hand here. On a real project I keep the file and the code in step with contract tests, which this demo does not show.

Try it

Read the Sample Orders API reference and download the OpenAPI file. If you need an API documented, or designed and built from the contract up, tell me what you have.

Book the free consult

Related

What I checked / where this comes from

  • The live page /demo/api/ (fetched 11 November 2026), including its "Design notes" and "Honest limits" sections.
  • The file /demo/api/openapi.json (OpenAPI 3.1.0; counts of paths, operations and schemas as stated on the page).
  • General practice: cursor pagination stays stable under inserts and deletes, and a webhook receiver should verify the signature over the raw body.