# 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 `openapi.json` at your project root and served as a runtime route.

## 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 non-nullable column is listed, so generated SDKs treat them as guaranteed-present rather than optional.
- **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 schemas

Actions defined with [`defineAction({ input: z.object(...) })`](/compiler/definitions/actions) get their full input schema lifted into the operation's `requestBody`. Simple `z.object({...})` shapes are extracted at compile time; for discriminated unions, refinements, recursive schemas, and bare-identifier references (`input: createPersonInput`), the compiler runs your action file through Zod 4's native `z.toJSONSchema()` during compilation so the OpenAPI request body and the MCP tool's `inputSchema` round-trip exactly what your Zod expression declares — no static translator to maintain.

## 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 openapi.json to project root
    publish: true,    // serve GET /openapi.json at runtime
  },
});
```

| Option | Default | Description |
|--------|---------|-------------|
| `generate` | `true` | Write `openapi.json` to the project root 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`.

The bundled Quickback CMS and Account SPAs do not import `api-types.gen.ts`. CMS is driven by `schema-registry.json`, and Account uses Better Auth plus small hand-written fetch helpers. Use `openapi.types` for your own colocated client apps.

### 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" } }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```
