apibreak docsnew in 0.3.0 · free · MIT
Check the API examples in your docs against your OpenAPI spec
Examples drift from the spec they describe, and nobody notices until a reader copies one and it fails. apibreak docs reads the curl, HTTP, fetch() and Python requests calls in your Markdown and lists the ones the spec does not support, with file:line and a fix.
npx apibreak docs --spec openapi.yamlNode 20+. Reads docs/**/*.{md,mdx} and README.md unless you pass files. No dependencies, no telemetry, nothing uploaded.
Want us to run it on your repo and read the findings for you? See pricing.
A real run, on listmonk's own API docs against its own spec (commit 82db22c, 2026-10-04):
~/listmonk $ npx apibreak docs --spec docs/swagger/collections.yaml 'docs/docs/content/apis/*.md' ## apibreak docs **14 errors** — 10 files scanned, 129 API references found: 128 parsed (126 checked, 2 on other hosts), 1 unparsed. Spec: `docs/swagger/collections.yaml` (Listmonk 1.0.0) ### Findings A 14-row Markdown table follows. Three of its rows are below, laid out for reading.~/listmonk $ echo $? 1
The listmonk run
Three of the 14 errors, as printed
- error
unknown-body-fielddocs/docs/content/apis/bounces.md:30GET http://localhost:9000/api/bounces?campaign_id=1&page=1&per_page=2
GET /bounces declares no request body, but the example sends
order,order_by,sourceFix hintThe spec has
order,order_by,sourceas query parameters of GET /bounces; send them in the query string.Checked against listmonk's router: the docs were wrong.
- error
wrong-methoddocs/docs/content/apis/subscribers.md:610POST http://localhost:9000/api/subscribers/query/blocklist
/subscribers/query/blocklist has no POST operation
Fix hintThe spec allows PUT on
/subscribers/query/blocklist.Checked against listmonk's router: the docs were wrong.
- error
unknown-query-paramdocs/docs/content/apis/lists.md:41GET http://localhost:9000/api/lists?status=active&per_page=100
query parameter "status" is not a parameter of GET /lists
Fix hintQuery parameters in the spec:
minimal,order,order_by,page,per_page,query,tag.Checked against listmonk's router: the spec is behind the server.
It reports that the docs and the spec disagree, not which one is wrong.
The line under each row above came afterwards, from reading listmonk's router. The first two are mistakes in the docs: a GET body the server never reads, and POST where the route is PUT. The third, and most of the other eleven, are the spec lagging the server. Both are worth a pull request; only you can tell which side to fix.
The same three rows as raw Markdown, and how to reproduce the run
| Severity | Where | Rule | Reference | Problem | Fix |
| **error** | `docs/docs/content/apis/bounces.md:30` | unknown-body-field | `GET http://localhost:9000/api/bounces?campaign_id=1&page=1&per_page=2` | GET /bounces declares no request body, but the example sends `order`, `order_by`, `source` | The spec has `order`, `order_by`, `source` as query parameters of GET /bounces; send them in the query string. |
| **error** | `docs/docs/content/apis/subscribers.md:610` | wrong-method | `POST http://localhost:9000/api/subscribers/query/blocklist` | /subscribers/query/blocklist has no POST operation | The spec allows PUT on `/subscribers/query/blocklist`. |
| **error** | `docs/docs/content/apis/lists.md:41` | unknown-query-param | `GET http://localhost:9000/api/lists?status=active&per_page=100` | query parameter "status" is not a parameter of GET /lists | Query parameters in the spec: `minimal`, `order`, `order_by`, `page`, `per_page`, `query`, `tag`. |git clone https://github.com/knadh/listmonk && cd listmonk && git checkout 82db22c
npx apibreak docs --spec docs/swagger/collections.yaml 'docs/docs/content/apis/*.md'Input
What it reads
Fenced code blocks and inline references in Markdown and MDX, checked against an OpenAPI 3.x or Swagger 2.0 spec, JSON or YAML, local or by URL.
curl commands
curl https://api.example.com/v1/things?limit=10
Raw HTTP request blocks
POST /v1/things HTTP/1.1
JavaScript fetch()
fetch("https://api.example.com/v1/things", { method: "POST" })
Python requests and httpx
requests.get("https://api.example.com/v1/things")
Inline references in prose
`GET /v1/things/{id}`
How paths are matched
The spec's server URLs are stripped from documented URLs.
{id},:id,<id>,{{id}}and concrete values likecus_123all fit a{customer_id}template.
The samples assume the spec's servers include api.example.com. If your docs call http://localhost:3000/api, pass it as --base-url; a URL on any other host is skipped and counted, not checked.
Output
What it reports
Each finding carries file:line, the snippet and a fix hint.
| Rule | Severity |
|---|---|
unknown-endpointNo spec path fits. The closest one is suggested. | error |
wrong-methodThe path exists, the method does not. | error |
unknown-body-fieldA top-level body field the request schema does not list. | error |
unknown-query-paramA query parameter the operation does not declare. | error |
missing-requiredA complete example that leaves out a required field or parameter. | error |
deprecated-operationThe example calls an operation the spec marks deprecated. | warning |
deprecated-fieldThe example sends a field the spec marks deprecated. | warning |
Limits
What it skips, and how it tells you
It errs towards silence rather than guessing. Calls it cannot parse, URLs on other hosts and request bodies it cannot read are counted in the report as not checked. Where the spec itself is too loose to judge, it switches that check off; those cases are listed below.
- Calls it cannot read, such as a URL held in a variable or a computed method, are listed as unparsed rather than guessed at.
- A URL on another host is skipped and counted. Pass
--base-urlto include it. - Each of these switches the dependent check off: a body schema with
additionalProperties, a$refit cannot resolve, a body read from a file or a variable, a query string with a variable standing in for a whole parameter, and two path templates that fit equally well. - Silence on purpose with
<!-- apibreak-ignore -->for one line or the next code block (the marker must be the whole comment), or<!-- apibreak-ignore-file -->for a whole file.
Under its table, the listmonk report also says:
1 unparsed reference (not checked) - `docs/docs/content/apis/subscribers.md:387` curl: no URL could be identified Skipped, host not in the spec's servers: `localhost:8080` ×2. Pass `--base-url` for a host your docs use to check those too. 6 request bodies were not read (a variable, a file, or not valid JSON), so their fields were not checked. Only fenced curl, HTTP, fetch() and Python requests/httpx examples and inline METHOD /path references are read; other languages and SDK calls are not checked. Body fields and query parameters are checked at the top level only.
CI
Run it on every pull request that touches the docs
The GitHub Action's inputs are built around the vendor check, so docs runs as a plain step. --summary "$GITHUB_STEP_SUMMARY" puts the findings table in the job summary.
# .github/workflows/apibreak-docs.yml
name: apibreak docs
on:
pull_request:
paths: ['docs/**', 'README.md', 'openapi.yaml']
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: npx apibreak docs --spec openapi.yaml --summary "$GITHUB_STEP_SUMMARY"- exit 0
- Nothing at or above --fail-on (default error).
- exit 1
- A finding at or above --fail-on.
- exit 2
- A usage error, a spec that could not be read, or no files to scan.
Full usage. --json writes the findings to a file; --fail-on warning also fails on deprecations; --base-url can be repeated.
apibreak docs --spec <file|url> [--base-url <url>]... [--json <path>] [--summary <path>]
[--fail-on error|warning|never] [files or globs...]apibreak diff uses the same exit codes. apibreak check predates them and keeps its own (2 a finding, 1 could not run), so a script that wraps both should test each separately.
Same package
The other two commands
apibreak diff <old> <new>
Compares two versions of any OpenAPI 3.x or Swagger 2.0 document, files or URLs, and lists breaking changes first. With --base-ref origin/main openapi.yaml, a pull request checks its own spec against the branch it targets.
apibreak check
The original check, for APIs you consume: list the GitHub or Stripe endpoints you call, and CI reports only the vendor spec changes that touch them.
How the vendor check worksPaid
Have us run it on your repo
The command above is free and yours to run forever. These two offers are us running it against your repository and reading the findings for you.
Checkout is not available on this deployment yet.
Nothing here is charged until it is set up.
Free, MIT, nothing to sign up for
npx apibreak docs --spec openapi.yamlEarly access: version 0.3.2, no SLA, and the interface may still change. The package is one unminified JavaScript file with no dependencies and no install scripts, built from the TypeScript source on GitHub, so you can read what runs before you run it.