Getting started
Getting started
Section titled “Getting started”This page walks through a simple server and the client.
For coding agents: Working with Coding Agents (copy-paste prompts; skill SKILL.md).
1. Install backend dependencies
Section titled “1. Install backend dependencies”npm i callspec runtyp expressnpm i -D tsx typescript @types/expressRequirements: Node.js 18+ (runtime), TypeScript 5+, Express 4.x (peer), runtyp 2.5.0 (peer).
2. Define a route
Section titled “2. Define a route”import {route, err} from 'callspec';import {predicates as p, Infer} from 'runtyp';
const product = p.object({ id: p.string(), name: p.string(), priceCents: p.number(),});type Product = Infer<typeof product>;
const products: Product[] = [ {id: 'sku-1', name: 'Widget', priceCents: 999}, {id: 'sku-2', name: 'Gadget', priceCents: 1299},];
export const getProductById = route({ input: p.object({id: p.string()}), output: product, meta: {summary: 'Get product by ID', tags: ['catalog']}, auth: 'none', mcp: true, // Keep the handler inline for LSP support handler: async (input, _ctx) => { const found = products.find((item) => item.id === input.id); if (!found) return err.NOT_FOUND(); return found; },});Callspec validates input before your handler runs. Learn more about routes
3. Define the API
Section titled “3. Define the API”import {spec} from 'callspec';import {getProductById} from './routes/getProductById';
export const api = spec({ meta: {title: 'My API', version: '1.0.0'}, routes: {getProductById},});Related: spec · Server layout
4. Mount and run
Section titled “4. Mount and run”import {mountSpec} from 'callspec';import express from 'express';import {api} from './spec';
const app = express();const router = express.Router();
mountSpec(router, api); // parses application/json on this routerapp.use('/v1', router);
const port = 3000;app.listen(port, () => { console.log(`RPC: http://127.0.0.1:${port}/v1/getProductById`); console.log(`Docs: http://127.0.0.1:${port}/v1/docs`); console.log(`Callspec: http://127.0.0.1:${port}/v1/callspec.json`); console.log(`OpenAPI: http://127.0.0.1:${port}/v1/openapi.json`); console.log(`MCP: http://127.0.0.1:${port}/v1/mcp`);});npx tsx src/index.tsOpen http://127.0.0.1:3000/v1/docs.
Related: Docs UI · mountSpec · MCP Server · OpenAPI
5. Generate the SDK
Section titled “5. Generate the SDK”# Live mount (server running) — pass the mount pointnpx callspec http://127.0.0.1:3000/v1 --output src/generated/api.ts
# Optional — pin the contract for CI / offline codegencurl -fsS http://127.0.0.1:3000/v1/callspec.json -o callspec.jsonnpx callspec ./callspec.json --output src/generated/api.tsRelated: SDK generation · Shared validation (schemas from codegen)
6. Call from your app
Section titled “6. Call from your app”Each method returns a Result — check result.ok, handle the codes that matter for that UI, and send the rest through a shared helper (you do not need a giant switch at every call site).
import {ApiClient} from '../generated/api';
const api = new ApiClient({ baseUrl: 'http://127.0.0.1:3000/v1',});
export async function fetchProduct(id: string) { const result = await api.getProductById({id});
if (!result.ok) { if (result.code === 'NOT_FOUND') { console.error(`Unknown sku ${id}`); return null; } console.error(result.code, result.data); return null; }
return result.value; // { id, name, priceCents }}
const product = await fetchProduct('sku-1');console.log(product?.name, product?.priceCents);Shared helper + optional exhaustive switch: Client usage.
Related: Client usage · Builtin errors · Authentication