route & spec
route & spec
Section titled “route & spec”route() wires one HTTP/MCP endpoint. spec() collects those routes plus spec-level metadata, optional shared preds, and auth. Pass the result to mountSpec() to serve RPC, the docs UI, callspec.json, OpenAPI, and MCP.
route({ input, output, meta, resolver, … })| Option | Default | Description |
|---|---|---|
input | — | Runtyp pred for the request body (POST JSON). Validated before your resolver runs. |
output | — | Runtyp pred for a successful response. |
meta | — | Docs/OpenAPI/MCP labels — see Route meta below. |
resolver | — | (input, ctx) => output | failure. Must accept exactly (input, ctx). |
errors | — | Domain error codes from defineErrors(). Builtins (NOT_FOUND, UNAUTHORIZED, …) are always available — never declare those. |
auth | 'bearer' | 'none' — no token required. 'bearer' — missing/invalid token → 401 before the resolver. |
scope | 'public' | 'public' — included in docs, OpenAPI, SDK codegen, MCP tools/list. 'private' — server-only; omitted from exports. |
mcp | — | Expose as an MCP tool. true, or { name?, annotations? } to override the tool name or MCP annotations. |
Returns a wired route (WiredRoute) for spec({ routes }). Call .resolver(input, ctx) in tests — no HTTP. See Resolvers and Unit testing.
Route meta
Section titled “Route meta”Every route needs meta with at least summary and tags. These show up in the docs UI route list, OpenAPI operation text, and MCP tool titles.
| Field | Required | Description |
|---|---|---|
summary | yes | Short label — docs sidebar, OpenAPI summary, MCP tool title. |
tags | yes | Grouping in the docs UI and OpenAPI tags (e.g. ['catalog'], ['users']). |
description | no | Longer prose for OpenAPI/MCP when the summary is not enough. |
spec({ routes, meta?, exports?, authenticate? })| Option | Default | Description |
|---|---|---|
routes | — | Map of wired routes — see Routes map. |
meta | {} | Spec title, docs UI branding, OpenAPI info, MCP server hints — see Spec meta. |
exports | — | Named runtyp preds for frontend codegen — see Exports. |
authenticate | — | Bearer hook — required when any route uses auth: 'bearer'. See Authentication. |
Throws at load time if any route uses auth: 'bearer' and authenticate is missing.
Routes map
Section titled “Routes map”Keys become RPC method names — routes: { getProductById } is called as POST /v1/getProductById (plus your Express mount prefix). Values must come from route({ …, resolver }), not bare preds.
Only routes with scope: 'public' appear in callspec.json, OpenAPI, SDK codegen, and MCP tools/list. Private routes still run on the server.
Spec meta
Section titled “Spec meta”meta is flat JSON on the spec. It flows into emitted documents and into the docs UI when you mountSpec. It does not turn docs on or off — that is mountSpec(router, spec, { docs?, docsPath? }) (mountSpec options).
| Field | Default | Used in | Description |
|---|---|---|---|
title | 'Callspec API' | Docs UI header, OpenAPI info.title, MCP server name | Display name for your API. |
version | '0.0.0' | OpenAPI info.version, MCP server version | Semver or build id — your choice. |
intro | — | Docs UI home (required to show it), OpenAPI info.description | Welcome paragraph under the title. Without intro, the UI opens on the route list — no home page or MCP connect panel. |
website | — | Docs UI home link | { url, label? } — label defaults to the hostname or “Learn more”. |
logo | — | Docs UI header + home | { light, dark? } — image URLs; see Logo URLs. |
authHint | auto | Docs UI MCP connect panel (home page) | Prose about Bearer tokens shown in the connect UI. Auto-set when bearer routes exist unless you override. |
mcpInstructions | — | MCP server instructions field | Agent-facing server description returned by MCP initialize — not shown in the docs UI connect panel. |
Full whitelabel example (from the Chirp demo):
const meta = { title: 'Chirp API v2', version: '2.0.0', intro: 'Read and write posts, timelines, and DMs.', website: {url: 'https://chirp.social', label: 'chirp.social'}, logo: { light: './brand/mark-light.png', dark: './brand/mark-dark.png', }, authHint: 'Use Authorization: Bearer <token> for private routes.', mcpInstructions: 'Chirp API — use Bearer demo in this sandbox.',};
export const api = spec({meta, routes: {getUserById, …}});Logo URLs
Section titled “Logo URLs”Paths in logo.light / logo.dark are resolved relative to the docs UI URL (e.g. ./brand/mark.png under /v1/docs → /v1/docs/brand/mark.png). Use absolute URLs (https://…) when the asset is hosted elsewhere.
Serve files under the docs path on the same router:
router.use('/docs/brand', express.static(path.join(__dirname, 'brand'), {index: false}));mountSpec(router, api, {docsPath: '/docs'});// meta.logo.light: './brand/mark.png' → /docs/brand/mark.pngIf dark is omitted, the light logo is used in both themes.
Docs UI surfaces
Section titled “Docs UI surfaces”mountSpec serves the built-in explorer by default at {mount}/docs. The UI loads {mount}/callspec.json, lists public routes, lets you try RPCs, browse schemas, and connect MCP clients.
| What you want | Where to configure |
|---|---|
Turn docs/OpenAPI/callspec.json off | mountSpec(…, {docs: false}) |
| Change docs path only | mountSpec(…, {docsPath: '/explorer'}) — contract paths stay /callspec.json and /openapi.json |
| Title, intro, logo, website | spec({ meta: { … } }) |
| Per-route summaries and tags | route({ meta: { summary, tags, … } }) |
Exports
Section titled “Exports”Optional map of named runtyp preds that are not routes — shared form shapes, filters, enums for the frontend:
import {product, productList} from './schemas/product';
export const api = spec({ meta: {title: 'My API', version: '1.0.0'}, routes: {getProductById, listProducts}, exports: {product, productList},});exports land in callspec.json and power a second codegen pass:
npx callspec ./callspec.json --output src/generated/validators.ts --validatorsSee Shared validation and SDK generation.
authenticate
Section titled “authenticate”import type {Authenticate} from 'callspec';
export type Ctx = {userId: string};
export const authenticate: Authenticate<Ctx> = async (token, req) => { const session = await verifySession(token, req); return session ? {userId: session.userId} : undefined;};
export const api = spec({meta, routes, authenticate});Callspec extracts Authorization: Bearer …, calls your hook, and passes the returned context to resolvers on bearer routes. Return undefined for invalid tokens → 401. See Authentication and Request context.
← API reference · Next: mountSpec