OpenAPI
Auto-generated OpenAPI 3.1 spec from your router — per-procedure metadata, path parameters, security, and custom overrides.
Silgi generates a full OpenAPI 3.1.0 specification from your router automatically. Every procedure becomes an operation — input schemas become request bodies or query parameters, output schemas become response bodies, and typed errors become error responses.
Enable
Pass scalar: true to serve() or handler():
s.serve(appRouter, {
: true,
})This serves:
/api/openapi.json— the raw OpenAPI specification/api/reference— interactive API docs powered by Scalar
Response types
For the API reference to show a response schema, you must declare $output() on the procedure. Without it, the generated spec only includes a description — no schema is rendered in Scalar or the raw OpenAPI JSON.
// No $output — Scalar shows "Successful response" with no schema
const = s.$resolve(({ }) => .db.users.findMany())
// With $output — Scalar renders the full response type
const = s.$output(z.array(UserSchema)).$resolve(({ }) => .db.users.findMany())$output() serves two purposes: it validates the return value at runtime, and it populates the response schema in the
OpenAPI spec. Both benefits apply whenever you declare it.
Procedure metadata
Use $route() to enrich the generated OpenAPI spec with metadata. Paths are auto-generated from the router tree — you only need $route() for documentation metadata like tags, summary, and description:
const = s
.$route({
: 'Get user by ID',
: 'Returns a single user by their numeric ID.',
: ['Users'],
: 'getUserById',
: false,
: 200,
: 'User found',
})
.$input(z.object({ : z.number() }))
.$output(UserSchema)
.$errors({ : 404 })
.$resolve(...)| Option | Type | Default | Description |
|---|---|---|---|
summary | string | — | Short summary shown in API docs |
description | string | — | Detailed description (supports markdown) |
tags | string[] | auto from router | Tags for grouping operations |
operationId | string | auto from router | Custom operation ID for SDK generation |
deprecated | boolean | — | Mark as deprecated |
successStatus | number | 200 | HTTP status code for success response |
successDescription | string | 'Successful response' | Description for success response |
method | string | 'POST' | HTTP method — used for special cases like auth passthrough |
path | string | auto from router | Custom URL path — used for special cases like auth passthrough |
The method and path fields exist for special cases like wildcard passthrough handlers (e.g. .$route({ method: '*', path: '/api/auth/**' })). For normal procedures, paths are auto-generated from the router tree structure.
Auto-generated paths
OpenAPI paths are generated automatically from the router tree structure. Each procedure's position in the router becomes its path:
const = s.router({
: {
: listUsers, // → POST /users/list
: createUser, // → POST /users/create
: getUser, // → POST /users/get
},
: {
: listOrders, // → POST /orders/list
},
})No manual path definitions needed — the spec reflects your router structure exactly.
Security
Global security
Set a global security scheme in ScalarOptions:
s.serve(appRouter, {
: {
: {
: 'http',
: 'bearer',
: 'JWT',
},
},
})All operations will require this scheme by default.
Per-procedure override
Override security on individual procedures:
// Public endpoint — no auth required
const = s.$route({ : false }).$resolve(() => ({ : 'ok' }))
// Requires specific scheme
const = s.$route({ : ['bearerAuth'] }).$resolve(() => ({ : true }))| Value | Effect |
|---|---|
false | Public — security: [] in spec |
string[] | Named security schemes required |
| not set | Inherits global security |
Error responses
Typed errors
Errors declared with $errors() are automatically documented:
const = s
.$errors({
: 404,
: { : 403, : 'Not allowed' },
})
.$resolve(...)
// Spec generates: 404 and 403 response entries with error schemaThe message field appears as default in the generated schema.
Validation errors
Procedures with $input() automatically get a 400 BAD_REQUEST response in the spec with the validation error schema — no configuration needed.
Guard errors
Errors from guards ($use(authGuard)) are merged into the procedure's error responses:
const = s.guard({
: { : 401 },
: () => { ... },
})
const = s
.$use()
.$errors({ : 403 })
.$resolve(...)
// Spec: 401 from guard + 403 from procedureCustom OpenAPI override
For anything not covered by the built-in options, use spec to override or extend the generated operation:
Object merge
s.$route({
: {
: { : 'https://docs.example.com/users' },
'x-rate-limit': 100,
'x-internal': true,
},
})Function override
Receives the auto-generated operation, return the final one:
s.$route({
: () => ({
...,
'x-codegen-request-body-name': 'body',
: [
...(.parameters ?? []),
{ : 'X-Tenant-ID', : 'header', : true, : { : 'string' } },
],
}),
})The spec override is applied last — after all auto-generation is complete. It's the escape hatch for any OpenAPI
feature not directly supported.
Schema conversion
Silgi uses Standard Schema to convert your validator schemas (Zod, Valibot, ArkType) to JSON Schema. This happens via the ~standard.jsonSchema.input() interface — no Zod-specific introspection needed.
This means the generated OpenAPI spec is accurate for any validator that implements Standard Schema v1, not just Zod.
Tags
Tags are auto-generated from the first router segment:
const = s.router({
: { // tag: "users"
: ...,
: ...,
},
: { // tag: "orders"
: ...,
},
})Override with $route({ tags: ['Custom', 'Tags'] }).
What's next?
- Code Generation — generate Silgi routes from an OpenAPI spec (the reverse direction)
- Server — Scalar UI configuration and CDN options
- Procedures —
$input(),$output(),$errors()API - Middleware — guards with typed errors