Skip to content

Process and tooling

This page explains how the Reading List API documentation was built and why I made the choices I did. The workflow is OpenAPI-first: I wrote the API description before any other page, generated the reference from it, and wrote the guides by hand around it.

Goal

I wanted one sample that shows both halves of modern API documentation: structured, tool-friendly source material that generators and mock servers can consume, and human-written guides that no generator produces. The API is small on purpose, so that every page can be finished and checked against the others.

Tooling

Tool Version Used for
OpenAPI Specification 3.1.0 Describing the API
Redocly CLI 2.63.0 Linting the specification and generating the HTML reference
Prism 5.16.0 Running a local mock server from the specification
MkDocs with Material for MkDocs n/a Publishing the guides
Git and GitHub Pages n/a Source control and hosting

I pinned the Redocly CLI and Prism versions in every command rather than using @latest, so the output is repeatable and a future release cannot change the results unexpectedly.

Decisions and tradeoffs

A fictional API with three endpoints. Searching for a real, undocumented API would have cost weeks and left me documenting behavior I could not verify. A fictional API I control lets me document every behavior and check it. Each endpoint exists to demonstrate something specific: GET /books covers query parameters, an enum filter, and a default value; GET /books/{bookId} covers a path parameter and a 404; POST /books covers a request body, validation, and a 201.

OpenAPI 3.1. Every tool in this workflow accepted 3.1 without conversion: the same file lints in Redocly CLI, generates a reference, and drives a Prism mock.

Specification first. Writing the YAML before the guides meant every example in the guides could be copied from the specification instead of invented separately. When an example changed, I changed it in one place.

Problem Details for errors. RFC 9457 gives every error the same machine-readable shape, which makes the error guide shorter and the client code simpler. The standard allows extension members, so I added an errors array that names each invalid field. A bare "the request body is invalid" does not tell a developer what to fix.

API key authentication. It is the simplest scheme that I could document completely, including key handling and rotation. A scheme like OAuth would have added flows that a local mock cannot demonstrate.

One copy of the specification. The YAML lives in the same folder as the pages. MkDocs publishes it unchanged for download, and the generator reads the same file, so the downloadable spec and the generated reference cannot drift apart.

Workflow

Run from the root of the portfolio repository:

# 1. Validate the specification
npx @redocly/cli@2.63.0 lint docs/projects/reading-list-api/reading-list-api.yaml

# 2. Generate the HTML reference
npx @redocly/cli@2.63.0 build-docs docs/projects/reading-list-api/reading-list-api.yaml \
  --output docs/projects/reading-list-api/api-reference.html

# 3. Build and preview the site
mkdocs serve

A small script, scripts/build-reading-list-api.sh, runs all of this in order and stops at the first failure. I rerun it whenever the specification changes, because the generated HTML does not update itself.

What I wrote and what was generated

Content Source
API structure: paths, parameters, schemas, status codes Written by hand in the OpenAPI file
Descriptions, examples, validation rules, code samples Written by hand in the OpenAPI file
Interactive reference page (api-reference.html) Generated by Redocly CLI from the OpenAPI file
Mock server responses Generated by Prism from the OpenAPI examples
Overview, Quickstart, Authentication, and Error handling guides Written by hand
Changelog and this page Written by hand

The generated reference is only as good as the descriptions in the specification. Generating it took one command. Writing field descriptions, examples, and the validation rules took most of the effort, and that is the part a reader sees.

What linting caught

My first draft of the specification passed validation but produced four warnings from Redocly CLI's recommended rules: a missing license in the info object, and a missing operationId on each of the three operations. I added them. The operationId values (listBooks, createBook, getBook) are also the names that code generators and tools such as Postman use.

Linting checks structure, not quality. It did not flag that the first draft had no descriptions on its schema fields, no query parameters, and only a one-line 400 example. I found those by reading the generated reference as a developer would, and then fixed them.

What the mock server does and does not do

Prism builds a mock from the specification. It is a good way to prove that the examples are well formed and the documentation is usable, but it is not a working API. I tested each behavior below against Prism 5.16.0 and recorded what it actually did.

Behavior The API as designed The Prism mock
Request without an X-API-Key header 401 401
Request with an unrecognized key 401 200: accepts any value
Key sent as a query parameter 401 401: the header is still missing
Unknown book ID 404 200 with an example book, unless the request sets Prefer: code=404
status filter on GET /books Returns only matching books Ignored
limit on GET /books Caps the number of books Ignored, but values outside 1 to 100 return 400
POST /books Stores the book and assigns an id Returns a fixed example; nothing is stored
Invalid request body 400 listing the fields that were invalid 400 with a fixed example body

Testing the mock also exposed a mistake in my own specification. My first version of GET /books reused the request-body 400 example, so a bad limit value appeared to produce an error about a missing title. I gave the operation its own example.

Because the mock behaves differently from a real server, the guides say so wherever it matters. A reader who follows the Quickstart should never be surprised by a result that the page did not warn them about.

Limitations and next steps

This version is intentionally small. If I were extending it, I would add:

  • PATCH /books/{bookId} to update a reading status. Today a book's status can be set only when it is created, which is the most obvious gap in the API.
  • DELETE /books/{bookId} to remove a book.
  • Pagination. limit caps the list, but there is no way to request the next page. Cursor-based pagination would fix that.
  • Rate limiting, with a 429 response and documented headers.
  • Error type pages. The type URIs are placeholders, and a production API would document each one.
  • Automated checks that run the code samples in CI, so the documentation fails the build when an example stops working.