# 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:

```lapis
[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](https://github.com/cr0hn/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`](https://www.npmjs.com/package/@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](https://docspack.dev/api) for a reader,
[`/api.lapis`](https://docspack.dev/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](https://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`](https://docspack.dev/api/example.json) and its LAPIS is
[`/api/example.lapis`](https://docspack.dev/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.
