OpenAPI Spec
Auto-generated OpenAPI 3.1 specification from your Quickback definitions.
The compiler generates an OpenAPI 3.1 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.jsonReturns the full OpenAPI spec as JSON.
curl https://your-api.example.com/openapi.jsonWhat'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.codeis a closed enum — everycodevalue the API can emit (AUTH_MISSING,DB_UNIQUE_VIOLATION,BATCH_SIZE_EXCEEDED, …) is enumerated so consumers canswitchexhaustively.- ID
format— only emitted asformat: "uuid"when your project'sgenerateIdactually 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(...) }) 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:
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)
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
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:
openapi: {
docs: 'scalar', // or 'swagger'
}| Value | UI | Notes |
|---|---|---|
"scalar" | Scalar API Reference — modern, lightweight | Recommended default |
"swagger" | 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
- Open Postman and click Import
- Paste the URL
https://your-api.example.com/openapi.jsonor upload the file - 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:
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 for a fully typed client:
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 |
// 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 — 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.jsonOr enable the in-project browser via the docs config option above and visit /docs directly.
Example output (excerpt)
{
"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):
[
{ "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. operationIds 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.