# API documentation example: what good docs look like

> Instead of describing good API documentation, this page takes a real developer portal apart and shows what each piece is for.

- **HTML:** https://offendersearch.app/docs/learn/api-documentation-example
- **Base URL (live):** https://api.offendersearch.app — live keys (`os_live_…`)
- **Base URL (sandbox):** https://sandbox.offendersearch.app — test keys (`os_test_…`), never billed
- **Authentication:** `X-API-Key` request header
- **OpenAPI:** https://offendersearch.app/openapi.json · https://offendersearch.app/openapi.yaml
- **Docs index for agents:** https://offendersearch.app/llms.txt

## What API documentation is for

API documentation has one job: get a developer from "I have a key" to "I have a correct integration in production" with as few surprises as possible. Everything in a good portal serves one of four questions — *what can I do?* (reference), *how do I do it?* (guides), *what does it look like?* (examples), and *what happens when it goes wrong?* (errors). A page that answers none of those is decoration.

The example on this page is the [OffenderSearch developer portal](/docs): a public-records API with five products, one platform, a sandbox and a generated reference. It is used here because every part of it can be opened and tried, not because it is the only way to do it. The portals developers most often point at as examples — Stripe, GitHub and Twilio — follow the same structure with different furniture.

## The sections every API doc needs

| Section | Answers | In the example |
| --- | --- | --- |
| Overview | What the API is, who it is for, what it is not | [/docs](/docs) and each product's introduction, e.g. [Sex Offender Search](/docs/sex-offender) |
| Getting started | Account, key, first request, first response, in under five minutes | [Getting started](/docs/platform/getting-started), then a [quickstart](/docs/quickstart) per product |
| Authentication | The header, key lifecycle, environments | [Authentication & keys](/docs/platform/authentication) |
| Core concepts | The ideas you must hold to read the responses: matching, confidence, completeness, freshness | [Matching](/docs/matching), [Result completeness](/docs/result-completeness), [Freshness](/docs/freshness) |
| API reference | Every endpoint, parameter, field and status, generated from the contract | [API reference](/docs/api-reference), with the OpenAPI at [/openapi.yaml](/openapi.yaml) |
| Data reference | Every enum value and object shape | [Enums](/docs/enums), the [record object](/docs/record-object) |
| Guides | Task-shaped walkthroughs, in learning order | [Guides](/docs/guides) per product |
| Errors | The error body, every status, what to retry | [Errors, rate limits & retries](/docs/platform/errors-and-retries) |
| Sandbox | Somewhere to run everything before it costs money | [Sandbox](/docs/sandbox), with fixed test names |
| Changelog | What changed, dated | [Changelog](/docs/changelog) |
| Machine-readable copies | The same content for tools and agents | OpenAPI, a `.md` twin of every page, [/llms.txt](/llms.txt), [/docs/all.md](/docs/all.md), the [Postman collection](/postman) |

## Reference vs guide vs tutorial: three kinds of page

The most common documentation failure is one kind of page pretending to be another. A **reference** page is complete and boring on purpose: every parameter, every field, every status, in a fixed order, generated from the contract so it cannot drift. A **guide** is a task — "screen a visitor at the front desk", "handle an incomplete result" — written in the order a developer does it, linking into the reference for detail. A **tutorial** teaches a concept with a runnable example and may have nothing to do with the product, which is what the pages under [/docs/learn](/docs/learn) are.

- Reference pages are generated; guides are written. If you find yourself hand-writing a parameter table, you are about to publish something that will be wrong by spring.
- A guide ends with the next guide. The sex offender guides run from [first search](/docs/guides/sex-offender/first-search) to [go live](/docs/guides/sex-offender/go-live) in reading order.
- A tutorial ends with a sign-up, not a sales pitch: the reader came to learn, and the product was the example.

## OpenAPI and Swagger documentation example

"Swagger" is the old name for what is now the OpenAPI Specification: a YAML or JSON description of every path, operation, parameter, schema and response. It is the single most useful documentation artefact because it is also an input — to reference generators, to [Postman](/postman), to SDK generators, to mock servers and to AI agents. The example portal publishes its contract at [/openapi.yaml](/openapi.yaml) and [/openapi.json](/openapi.json); the reference pages render from the same file.

*An excerpt of the published OpenAPI document (trimmed)*
```plain
openapi: 3.1.0
info:
  title: Offendersearch API
servers:
  - url: https://api.offendersearch.app
    description: Live API. Live keys (os_live_…); calls are billed.
  - url: https://sandbox.offendersearch.app
    description: API sandbox — same paths and shapes, test keys (os_test_…) only, never billed.
paths:
  /v1/search:
    post:
      summary: Synchronous search (PRIMARY — call and wait)
      security: [{ ApiKeyAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SearchRequest' }
      responses:
        '200': { description: Scored, de-duplicated results with per-source status }
        '401': { description: Missing, revoked or wrong-host API key }
        '422': { description: The query was refused; detail says which field }
        '429': { description: Rate limited; Retry-After carries the seconds to wait }
```

What makes an OpenAPI document *documentation* rather than a schema dump: a `summary` a human would write, a `description` that says what the operation is for and when not to use it, `example` values on request fields, every error status listed with its meaning, and the two servers (sandbox and live) declared so a tool can switch hosts by configuration.

## Request and response examples that can be run

An example a reader cannot run is an illustration. Every example in the portal is a real request against the sandbox with a synthetic person, and the response shown is what the sandbox returns. This request searches for a name the sandbox does not list, so the answer is the one every integration must handle first: complete, zero records.

*Request*
```bash
curl -X POST https://sandbox.offendersearch.app/v1/search \
  -H "X-API-Key: $OFFENDERSEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": {"firstName": "Jordan", "lastName": "Rivera", "dob": "1988-04-12"}}'
```

*Response (trimmed)*
```json
{
  "status": "complete",
  "counts": {
    "records": 0,
    "recordsReturned": 0,
    "sourcesQueried": 58,
    "sourcesComplete": 58,
    "sourcesIncomplete": 0
  },
  "records": [],
  "sourceStatus": [
    { "source": "OH", "status": "complete", "matched": 0 }
  ],
  "sandbox": true
}
```

- Show the request **and** the response. Half the questions a developer has are about the shape of what comes back.
- Use one consistent synthetic subject across the whole portal — here Jordan Rivera, born 1988-04-12 — so examples compose.
- Point the example at the sandbox, and say what the sandbox returns for it, so the reader can confirm their setup matches the page.
- Show the code in the three languages most readers use (curl, Python, JavaScript) and keep them byte-for-byte equivalent in behaviour.

## Error documentation example

Errors are where documentation earns its keep, because they are read at the worst moment. The example portal documents one error body shape per product, every status with its meaning, and — the part most docs skip — **what to retry**. A validation error names the field:

*A documented validation error*
```http
HTTP/1.1 422 Unprocessable Entity
{
  "detail": [
    { "loc": ["body", "query", "dob"], "msg": "value is not a valid date", "type": "value_error.date" }
  ]
}
```

The rule the docs state and the examples follow: branch on the status and on a machine-readable code, never on the sentence. Designing those codes is the subject of [API errors](/docs/learn/api-errors).

## Developer docs an AI agent can read

A growing share of integrations are now written with a coding assistant in the loop, and an assistant reads documentation differently from a person: it wants the whole thing, as text, in one fetch. The example portal is built so that every HTML page has a Markdown twin at the same path with `.md` appended, the entire portal is one file at `/docs/all.md`, and `/llms.txt` is the index an agent reads first. The HTML and the Markdown are rendered from one source, so they cannot disagree.

*The machine-readable copies, from the command line*
```bash
# Every page in the portal has a Markdown twin at the same path + .md
curl -s https://offendersearch.app/docs/learn/api-documentation-example.md | head

# The whole portal as one file, for an agent's context window
curl -s https://offendersearch.app/docs/all.md | wc -c

# The index an agent reads first
curl -s https://offendersearch.app/llms.txt | head -20

# The machine-readable contract
curl -s https://offendersearch.app/openapi.json | python3 -c "import json,sys; print(len(json.load(sys.stdin)['paths']), 'paths')"
```

The same idea in the other direction: the [MCP server](/mcp-server) exposes the API as tools an agent can call, and the [AI section](/docs/ai) documents how to give an agent the API safely. Documentation for agents is not a separate product; it is the same documentation in a shape a program can consume.

## API documentation best practices (the checklist)

- **Generate the reference from the contract.** Hand-written parameter tables drift. OpenAPI in, reference out.
- **Five-minute getting started.** Key, host, one request, one response, one error. Nothing else on that page.
- **Runnable examples with a real response**, against a sandbox, with a consistent synthetic subject.
- **Document errors as a contract**: body shape, every status, machine codes, retry rules, rate-limit headers.
- **One page per concept, one audience per page.** A page that explains matching to a developer and sells screening to a buyer serves neither.
- **Say what the API does not do.** Coverage limits, unsupported fields, things that look like features and are not. The example portal has a "what this does not cover" section on its tutorials for exactly this reason.
- **Date every page** and keep a changelog. A reader needs to know whether the page predates the behaviour they are seeing.
- **Publish machine-readable copies**: OpenAPI, Markdown twins, an `llms.txt`, a Postman collection.
- **Keep titles under 60 characters and descriptions under 160**, with the page's subject in both — documentation is found through search before it is read.
- **Link forward and back.** Every guide links the reference it relies on; every reference page links the guide that uses it.

## API doc generators and tools

| Tool | What it does | Input |
| --- | --- | --- |
| Swagger UI / Redoc | Render an OpenAPI document as a browsable reference | OpenAPI |
| Stoplight, ReadMe, Mintlify, Redocly | Hosted portals: reference from OpenAPI plus written guides | OpenAPI + Markdown |
| Postman | Collections with descriptions and saved examples, publishable as documentation | A collection, importable from OpenAPI |
| Docusaurus, Nextra, a static site generator | Guides and concept pages, with a reference component embedded | Markdown/MDX |
| Your own portal (the example) | A typed content model rendered once to HTML and once to Markdown | TypeScript objects + OpenAPI |

The choice matters less than the discipline: whatever renders the reference must read the contract, and whatever renders the guides must be versioned with the code. The example portal is a custom build because its pages must satisfy source-level tests — title budgets, markdown twins, every page in the sitemap — that a hosted tool could not enforce.

## Published API documentation examples developers cite

- **Stripe** ([docs.stripe.com](https://docs.stripe.com)) — the three-column layout with code in the right column, language switching that persists, and a reference where every object has a full example.
- **GitHub** ([docs.github.com/rest](https://docs.github.com/en/rest)) — exhaustive parameter and response documentation per endpoint, with the exact status codes each can return.
- **Twilio** ([twilio.com/docs](https://www.twilio.com/docs)) — tutorials that teach a task end to end in several languages, with the product as the example rather than the subject.
- **OffenderSearch** ([/docs](/docs)) — the example on this page: product sections, a shared platform section, generated reference, enums, guides, sandbox, Markdown twins, `llms.txt`, a [Postman collection](/postman) and [SDKs](/sdk).

## API documentation FAQ

### What is an api documentation example?

A real developer portal you can read to see how the pieces fit: an overview, getting started, authentication, a generated API reference, guides, runnable request and response samples, an error contract, a sandbox and a changelog. This page uses the OffenderSearch portal as that example.

### What should api documentation include?

At minimum: how to authenticate, every endpoint with its parameters and responses, at least one runnable example per endpoint, the error body and every status, rate limits, and a place to try it. The table above maps each to a page in the example.

### What is the difference between api documentation and api reference?

The reference is one part of the documentation: the complete, generated catalogue of endpoints and fields. Documentation also includes the guides, concepts, examples and errors that make the reference usable.

### What are swagger documentation examples?

OpenAPI documents (Swagger is the former name) rendered as a reference. Any public OpenAPI file is one; the excerpt above is from the example portal's own, published at /openapi.yaml.

### Which api doc generator should I use?

For a reference, anything that reads OpenAPI (Swagger UI, Redoc, or a hosted portal). For guides, a documentation site generator with versioning. The important property is that the reference is generated from the contract, not typed.

### How do I write api documentation for beginners?

Start with a five-minute getting-started page that ends in a real response, then one guide per task. Keep the reference separate and complete. Use one synthetic subject in every example so a beginner can follow the thread.

## What this page does not cover

- Writing an OpenAPI document from scratch; the excerpt shows the shape, not the full specification.
- Documentation for GraphQL or gRPC APIs, which have their own schema and introspection conventions.
- Internal engineering documentation (ADRs, runbooks). This page is about documentation for the people who call an API.
- A review of hosted documentation vendors. The tools table names categories; pick by whether the reference is generated and the guides are versioned.

## Related

- [RESTful API example: one request, one response](https://offendersearch.app/docs/learn/restful-api-example)
- [API errors: designing codes a client can act on](https://offendersearch.app/docs/learn/api-errors)
- [Postman API documentation: public workspace, live sandbox](https://offendersearch.app/docs/learn/postman-api-documentation)
- [API token example](https://offendersearch.app/docs/learn/api-token-example)
- [The API reference](https://offendersearch.app/docs/api-reference)
- [Getting started in five minutes](https://offendersearch.app/docs/platform/getting-started)
- [The sandbox and its fixed test names](https://offendersearch.app/docs/sandbox)
- [Guides](https://offendersearch.app/docs/guides)
- [Docs for AI agents and coding assistants](https://offendersearch.app/docs/ai)
- [Background check API](https://offendersearch.app/background-check-api)
- [Top 5 background check APIs, compared](https://offendersearch.app/blog/best-background-check-api)
- [All learning guides](https://offendersearch.app/docs/learn)
