# OpenAPI Spec

The compiler generates an [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0) specification from your feature definitions. By default it's both written to `src/apps/__quickback/spec/openapi.json` and served as a runtime route. It lives under the assets directory rather than the project root so the multi-MB blob stays out of the Worker bundle — see [Output](/tooling/output#spec-blobs-are-assets-not-bundle).

## Endpoint

```
GET /openapi.json
```

Returns the full OpenAPI spec as JSON.

```bash
curl https://your-api.example.com/openapi.json
```

## What's included

The generated spec documents every route the compiler produces:

- CRUD endpoints (list, get, create, update, delete)
- Batch operations
- Views
- Actions (including standalone actions)
- Auth routes (`/api/auth/**`)
- Storage, embeddings, and webhook routes (when configured)

Each endpoint includes request/response schemas derived from your Drizzle column types, access control metadata, and error responses.

### Schema fidelity

The generated component schemas carry enough type information that strict validators (zod, openapi-typescript, openapi-fetch) produce non-optional, narrowed types out of the box:

- **`required[]` on response components** — every selected column is listed; nullable columns are present as `T | null`, not optional.
- **Logical q types survive database lowering** — `q.bool()` remains `boolean`, `q.timestamp()` remains an ISO date-time string, and `q.url()` retains its URI format even when D1 stores the value as text or integer.
- **Closed enums on `text(_, { enum: [...] })` columns** — emitted as `{ type: "string", enum: [...] }`, narrowing to a literal union in TS.
- **`Error.code` is a closed enum** — every `code` value the API can emit (`AUTH_MISSING`, `DB_UNIQUE_VIOLATION`, `BATCH_SIZE_EXCEEDED`, …) is enumerated so consumers can `switch` exhaustively.
- **ID `format`** — only emitted as `format: "uuid"` when your project's `generateId` actually produces UUIDs. cuid2 / prefixed / nanoid / serial generators get plain `{ type: "string" }`, keeping strict OpenAPI validators happy.

### Action input and output schemas

Actions defined with [`defineAction({ input, output })`](/define/actions) get both Zod schemas lifted into the operation. `GET` inputs become typed query parameters; other methods use a JSON request body. A declared `output` becomes the success response schema and the generated helper requires `execute` to return a compatible value.

The CLI evaluates only the schema declarations in an isolated local bundle and converts them with Zod 4's `z.toJSONSchema()`. It never invokes `execute`, and the hosted compiler only receives plain JSON Schema data. Discriminated unions, refinements, recursive schemas, and shared schema references therefore flow to OpenAPI and generated TypeScript without relying on the compiler's static fallback parser.

## Configuration

Both generation and publishing default to `true`. You can control them independently in `quickback.config.ts`:

```typescript
export default defineConfig({
  name: "my-app",
  // ...
  openapi: {
    generate: true,   // write the spec into src/apps/__quickback/spec/
    publish: true,    // serve GET /openapi.json at runtime
  },
});
```

| Option | Default | Description |
|--------|---------|-------------|
| `generate` | `true` | Write `openapi.json` into `src/apps/__quickback/spec/` during compilation |
| `publish` | `true` | Serve the spec at `GET /openapi.json` (requires `generate: true`) |
| `docs` | `false` | Mount an interactive API browser at `GET /docs` — `"scalar"`, `"swagger"`, or `false` |

Omitting the `openapi` key entirely is equivalent to `generate: true, publish: true, docs: false`.

### Generate only (no runtime route)

```typescript
openapi: {
  generate: true,
  publish: false,
}
```

The file is still written so you can use it in CI or commit it to your repo, but no route is added to the app.

### Disable entirely

```typescript
openapi: {
  generate: false,
}
```

No file is written and no route is served.

### Interactive API browser at `/docs`

Mount an in-browser API reference by setting `openapi.docs`:

```typescript
openapi: {
  docs: 'scalar',   // or 'swagger'
}
```

| Value | UI | Notes |
|---|---|---|
| `"scalar"` | [Scalar API Reference](https://scalar.com/) — modern, lightweight | Recommended default |
| `"swagger"` | [Swagger UI](https://swagger.io/tools/swagger-ui/) — the familiar standard | Good if your team expects it |
| `false` / unset | No `/docs` route | Default |

Both UIs are loaded from CDN in a generated static HTML route — **no npm package is added to your project, no Hono middleware is installed, and the runtime bundle stays the same size**. The flag is purely compile-time: it toggles whether a `/docs` route is emitted.

Requires `publish: true` (the default). When `publish` is off, `/docs` is silently skipped — there's no spec for it to read.

## Usage with tools

### Import into Postman

1. Open Postman and click **Import**
2. Paste the URL `https://your-api.example.com/openapi.json` or upload the file
3. Postman creates a collection with every endpoint pre-configured

### Generated TypeScript types (`api-types.gen.ts`)

The compiler emits `api-types.gen.ts` next to `openapi.json` by default — a fully typed view of every route, request body, and response, ready to import in client code:

```typescript
import type { paths, components } from '../api-types.gen';

type Job = components['schemas']['Job'];
type ListJobsResponse = paths['/api/v1/jobs']['get']['responses']['200']['content']['application/json'];
```

Pair it with [`openapi-fetch`](https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-fetch) for a fully typed client:

```typescript
import createClient from 'openapi-fetch';
import type { paths } from '../api-types.gen';

const client = createClient<paths>({ baseUrl: 'https://your-api.example.com' });
const { data, error } = await client.GET('/api/v1/jobs', { params: { query: { limit: 25 } } });
```

#### Configuration

| `openapi.types` | Behavior |
|---|---|
| `true` (default) | Write `api-types.gen.ts` to the project root |
| `false` | Don't emit the types file |
| `"src/lib/api-types.ts"` | Custom output path (relative to project root) |
| `["api-types.gen.ts", "apps/web/src/lib/api-types.gen.ts"]` | Write the same generated types to multiple paths in one compile |

```typescript
// quickback.config.ts
export default defineConfig({
  // ...
  openapi: {
    types: ['api-types.gen.ts', 'apps/web/src/lib/api-types.gen.ts'],
  },
});
```

The file is generated by piping the in-memory spec through [openapi-typescript](https://github.com/openapi-ts/openapi-typescript) — you can keep running it manually if you want a different output path or version, but for the typical case there's nothing to wire up. If your API project and SPA live in the same repo, prefer multiple `openapi.types` outputs over a second manual `codegen:api` step so one `quickback compile` keeps both copies in sync. Skipped automatically when `openapi.generate` is `false`.

For application code, prefer the compact first-party [`quickback.gen.ts` type tree](/api/typescript). Keep `api-types.gen.ts` for `openapi-fetch` and third-party OpenAPI tooling.

### External viewer

Point any OpenAPI-compatible viewer at the endpoint:

```
https://petstore.swagger.io/?url=https://your-api.example.com/openapi.json
```

Or enable the in-project browser via the [`docs` config option](#interactive-api-browser-at-docs) above and visit `/docs` directly.

## Example output (excerpt)

```json
{
  "openapi": "3.1.0",
  "info": {
    "title": "my-app API",
    "version": "1.0.0"
  },
  "paths": {
    "/api/v1/jobs": {
      "get": {
        "summary": "List jobs",
        "operationId": "listJobs",
        "parameters": [
          { "name": "limit", "in": "query", "schema": { "type": "integer" } },
          { "name": "offset", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Job" } }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

## Operations manifest

Alongside the spec, every compile writes `reports/operations.manifest.json` — a
stable, machine-readable index of the full route surface (resource, action,
view, and auth routes; since v0.47 also the realtime, storage, and FGA
surfaces):

```json
[
  { "operationId": "listCustomers", "method": "GET", "path": "/api/v1/customers", "auth": "session", "feature": "customers", "kind": "crud" }
]
```

The manifest is a public contract: entries are sorted by `operationId`, carry
no timestamps, and the shape is extend-only — new fields may be added, existing
ones won't change meaning. `operationId`s are stable across compiles for the
same configuration, which makes the manifest the recommended integration point
for agent/MCP tooling, route inventories, and diff-based change review.

A sibling artifact, `reports/conformance.manifest.json`, enumerates the
expected authorization outcome for every route × principal class (derived from
the same compiler model that emits the enforcement code). The compiler's own CI
executes it against a running build on every change.
