docspack v1.2.0
Documentation

Documenting an HTTP API

An HTTP API is the one documentation topic most projects already describe in a machine-readable file. docspack build --openapi reads that file and writes one chunk per operation:

bash
npx docspack build --openapi ./openapi.json --name @acme/docspack --pkg-version 1.4.0

Combine it with --from to publish prose and an API in one package, which is what a library with both usually wants:

bash
npx docspack build --from ./docs --openapi ./openapi.json

Or put both in the docspack key of the package’s package.json, so docspack build with no arguments does the same thing:

json
{
  "docspack": {
    "from": "./docs",
    "openapi": "./openapi.json",
    "documents": "acme@1.4.0"
  }
}

What one chunk contains

Everything needed to make the call, and nothing else: the base URL, the credential, the inputs with their types, the body shape, the response, the failures, the types they reference, and a runnable curl line. The heading is the endpoint, the operation’s own prose comes first, and the rest is one block:

plaintext
[meta]
api: Invoice Service
base: https://api.example.com/v2
auth: bearer header:Authorization

[ops]
createInvoice POST /invoices
  Creates a draft invoice.
  > body: InvoiceCreate
  < Invoice  # 201 The created invoice.
  @auth bearerAuth

[types]
InvoiceCreate:
  customer_id: str
  lines: [LineItem]
  due_date?: date

[errors]
401 Error  # The token is missing, expired or invalid.
422 Error  # The line items did not validate.

That notation is LAPIS, an open format for describing an API to a model rather than to a code generator. It is used here rather than JSON because the same facts cost far less. Measured against Stripe’s published document — 594 operations, 1,454 schemas — one operation’s chunk is around 830 tokens. The document it came from is two million.

A chunk carries what the operation reaches, bounded. Where a shape was too large to expand, the chunk says so — +32 more, {...}, # not expanded here: — rather than presenting a partial shape as a whole one.

Asking for one request

An endpoint is a key, not a phrase. Ranked as prose, POST /v1/charges matches every chunk that mentions charges, and the overview page usually wins. So every operation chunk answers to both spellings of its endpoint, looked up exactly:

bash
docspack ask "POST /v1/charges"
docspack ask "createCharge"

A concrete URL reaches the template the document declared, which is the useful direction: what you have is an id out of a log line, and what the document contains is {charge}.

bash
docspack ask "GET /v1/charges/ch_3OxTmP2eZvKYlo2C"   # finds GET /v1/charges/{charge}

Naming no method matches every method on that path:

bash
docspack ask "what can I do with /v1/charges"

The MCP tool answers the same way, because it is the same code path.

Chunk ids are the endpoint

An operation’s chunk id comes from its method and path — post-v1-charges — not from its summary and not from its operationId. A summary gets reworded and an operationId gets renamed; the endpoint changes only when the endpoint changes. That matters because a chunk id is what docspack feedback add --chunk pins and what a published link resolves to.

JSON or YAML

Either, decided by what the file contains rather than by its extension — so a .yaml file holding JSON works too. The reader is @docspack/lapis, which is also a command of its own if all you want is the conversion:

bash
npx @docspack/lapis ./openapi.yaml --stats

What is not read

Swagger 2.0. Convert it first: npx swagger2openapi openapi.json -o openapi3.json.

A document split across files. An external $ref is refused rather than quietly dropped, because a schema replaced by “unknown” is a chunk claiming an endpoint takes no body. Bundle first: npx @redocly/cli bundle openapi.yaml -o openapi.json.

Each of these stops the build and names the command to run.

The page a person reads

The same document renders a reference — parameters, schemas, responses, code samples in cURL, JavaScript, Python and Go, and a console that sends the request — through @docspack/sheaf-react. docspack.dev does this with its own API: the reference for a reader, /api.lapis for an agent that needs the map, and /api/<operationId>.md for an agent that already knows which endpoint it wants.

That Invoice Service is an actual document in this repository, and it renders as a page too: docspack.dev/api/example — a bearer token, request bodies, a DELETE and an error table, which this site’s own read-only API has none of. The chunk above is cut down to fit; the page is the whole of it. Its document is /api/example.json and its LAPIS is /api/example.lapis: the whole API in about 900 tokens against 5,700 for the JSON. Open the two beside each other — that pair is the entire idea.

The console never stores a credential. It is read from the field when you press Send and kept in nothing that outlives the request.

Endpoints are not exported names

docspack verify compares a chunk’s entities against the names a library exports. POST /v1/charges is not one, so an operation chunk is neither accused of drift nor cleared of it. Proving an endpoint still exists means asking the server, which is a different tool from this one.

The same ranking docspack search uses, over the same 13 documents.