Silgi

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(...)
OptionTypeDefaultDescription
summarystringShort summary shown in API docs
descriptionstringDetailed description (supports markdown)
tagsstring[]auto from routerTags for grouping operations
operationIdstringauto from routerCustom operation ID for SDK generation
deprecatedbooleanMark as deprecated
successStatusnumber200HTTP status code for success response
successDescriptionstring'Successful response'Description for success response
methodstring'POST'HTTP method — used for special cases like auth passthrough
pathstringauto from routerCustom 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 }))
ValueEffect
falsePublic — security: [] in spec
string[]Named security schemes required
not setInherits 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 schema

The 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 procedure

Custom 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

On this page