# OpenAPI

> Generate an OpenAPI 3.1 spec + Swagger-UI docs from your defineRestRoute descriptors and (opt-in) your rpc procedures. Your routes ARE the API docs — nothing hand-maintained.



---

<!-- source: en/plugins/openapi.md -->
## OpenAPI

_Generate an OpenAPI 3.1 spec + Swagger-UI docs from your defineRestRoute descriptors and (opt-in) your rpc procedures. Your routes ARE the API docs — nothing hand-maintained._

`@voltro/plugin-openapi` turns your [`defineRestRoute`](/docs/data/rest-routes) descriptors — and, opt-in, your rpc procedures (queries / mutations / actions / streams) — into a live OpenAPI 3.1 document + a Swagger-UI page. The descriptors already carry method, path, input/output Schemas, `summary` and deprecation; the rpc procedures carry input/output/error Schemas. The plugin reads them, so there is **zero hand-written API documentation** to drift.

## Wiring

```ts
// app.config.ts
import { openapiPlugin } from '@voltro/plugin-openapi'
import listCustomers from './routes/v1/customers.list.route'
import createCharge from './routes/v1/charge.route'

export default {
  type: 'api' as const, name: 'api',
  restRoutes: [listCustomers, createCharge],
  plugins: [
    openapiPlugin({
      routes: [listCustomers, createCharge],          // the same descriptors
      info: { title: 'Payments API', version: '1.0.0' },
      // specPath: '/openapi.json',  docsPath: '/docs'  (defaults; docsPath:false disables the UI)
      // token: process.env.OPENAPI_DOCS_TOKEN,  // opt-in Bearer gate (see Permissions)
    }),
  ],
}
```

Mounts **`GET /openapi.json`** (the spec) and **`GET /docs`** (Swagger UI).

## What it maps

- Each route → an OpenAPI path + operation, keyed by method.
- The input Schema's `query` fields → `parameters` (in: query); `params` → path params; `body` → a JSON `requestBody`.
- The output Schema → the `200` JSON response.
- `summary` / `description` → operation metadata; `deprecated` → `deprecated: true`; `example.request` / `example.response` → request-body / 200-response examples; `sunset` → the `x-sunset` extension.
- The serve pipeline's real error statuses are documented per operation — `400` (input decode, when the route has an `input` schema), `401`/`403` (when it has `guards`), `410` (when `sunset` is set), `500` (always) — all referencing one shared `components.schemas.VoltroHttpError` envelope.
- When any route declares `guards`, the document publishes `components.securitySchemes.bearerAuth` and guarded operations carry `security: [{ bearerAuth: [] }]`.

Schemas are converted with effect's `JSONSchema.make`, so your `effect/Schema` definitions become JSON Schema verbatim. Pure: `generateOpenApiSpec(routes, info)` is exported + unit-tested if you want the spec without serving it.

## rpc procedures in the spec

Pass your rpc procedure descriptors via the `procedures` option and each query / mutation / action / stream is projected into the same spec. The framework's rpc surface is a **batched JSON-RPC protocol over one endpoint** (`POST /rpc`) — not a REST-per-resource API — so the projection is honest about that: every procedure becomes a `POST /rpc/<name>` operation whose body is the rpc **payload** (the `input` Schema), tagged `rpc` and flagged with an `x-voltro-rpc` extension. The operation description states the real transport; the path is a documentation projection, not a REST route you call directly.

```ts
import { openapiPlugin } from '@voltro/plugin-openapi'
import listOrders from './orders.list.query'
import createOrder from './orders.create.mutation'
import charge from './billing.charge.action'
import runAgent from './agent.run.stream'

openapiPlugin({
  routes: [listCustomers],                        // REST routes (optional)
  procedures: [listOrders, createOrder, charge, runAgent],  // rpc procedures
  info: { title: 'API', version: '1.0.0' },
})
```

Per procedure kind:

- **query** → `POST /rpc/<name>`; `input` → requestBody, `output` → the `200` response (documented as the *first snapshot*; the WebSocket rpc channel then pushes reactive updates).
- **mutation** / **action** → `input` → requestBody, `output` → `200`.
- **stream** → the `element` Schema is documented as a streaming response (`application/x-ndjson`, one element per line) — **not** faked as a single JSON value.
- The procedure's **typed error channel** (its `Schema.TaggedError` union) becomes a `422` response referencing the error schema; a procedure with no declared error documents only `200` + `500`.

Mixing rpc + REST in one spec is fine — the rpc operations are grouped under the `rpc` tag and flagged `x-voltro-rpc`, so a deployment can tell them apart from real REST routes. `procedures` is opt-in: omit it to keep the spec REST-only.

## Access control

Both routes publish your full API contract (every path, param, and schema), and both are **unauthenticated by default** — mount them only where that's intended. Two levers narrow the surface:

- `docsPath: false` hides only the Swagger-UI viewer; the spec at `/openapi.json` stays served.
- `token` (or the `OPENAPI_DOCS_TOKEN` env var) requires `Authorization: Bearer <token>` on **both** routes — a missing or mismatched header gets a `401`. The same shape as [`@voltro/plugin-prometheus`](/docs/plugins/prometheus)'s gate. Leave it unset behind a trusted network boundary; set it for a public-internet deployment.

```ts
openapiPlugin({
  routes: [listCustomers, createCharge],
  token: process.env.OPENAPI_DOCS_TOKEN,   // unset → both routes are public
})
```

## Permissions

The plugin declares no runtime permissions — it only mounts the two GET routes built from descriptors you already declared.
