Skip to content

Authentication

Credentials are per-route, not in the input pred.

authBehavior
'none'No token required — handler gets ctx: undefined unless the client sent a Bearer token and you wired authenticate
'bearer' (default)Missing or invalid token → 401 UNAUTHORIZED before the handler runs

Any route with auth: 'bearer' requires authenticate on the spec — spec throws at load time if it is missing.

src/auth.ts
import type {Authenticate} from 'callspec';
export type Ctx = {userId: string};
export const authenticate: Authenticate<Ctx> = async (token, req) => {
const session = await verifySession(token, req);
if (!session) return undefined;
return {userId: session.userId};
};
src/routes/getProfile.ts
import {route} from 'callspec';
import {predicates as p} from 'runtyp';
import type {Ctx} from '../auth';
export const getProfile = route({
output: p.object({userId: p.string()}),
meta: {summary: 'Get profile', tags: ['users']},
auth: 'bearer',
handler: async (_input, ctx: Ctx) => ({userId: ctx.userId}),
});

Pass the token on every call:

typescript
const api = new ApiClient({
baseUrl: 'http://127.0.0.1:3000/v1',
headers: () => ({Authorization: `Bearer ${getSessionToken()}`}),
});

Set meta.authHint. OpenAPI Bearer security is derived from route auth automatically.

scope: 'private' is documented when this mount uses visibility: 'all'. It does not change the auth gate. See Auth and scope.

For richer context from headers and JWT claims, see Request context.