API surfaces

One tRPC router, three ways to call it: tRPC, REST with OpenAPI, and MCP for AI agents.

tRPC: the primary surface

The application API is tRPC, defined in src/server/api/routers/ and registered in the root router. Procedures come in three flavors:

  • publicProcedure: no session required
  • protectedProcedure: requires an authenticated session
  • adminProcedure: requires the admin role

Context carries the enriched session (with role and onboarding state always fresh from the database). Client components call procedures through typed hooks via the tRPC provider; route loaders can call them server-side.

Adding an endpoint:

export const widgetRouter = router({
  list: protectedProcedure.query(({ ctx }) =>
    db.widget.findMany({ where: { userId: ctx.session.user.id } }),
  ),
});

Register it in src/server/api/root.ts and the client types update immediately.

REST + OpenAPI (optional)

With ENABLE_REST_API=true, the same router serves REST:

  • /api/rest/*: REST endpoints generated from procedures with openapi metadata
  • /api/openapi.json: the OpenAPI spec
  • /api/docs: interactive API reference

Expose a procedure by adding .meta({ openapi: { method: 'GET', path: '/users/me' } }) plus explicit zod .input() and .output() schemas (the generator requires both). Auth uses bearer tokens.

MCP server (optional)

With ENABLE_MCP=true, /api/mcp serves a Model Context Protocol endpoint so AI agents can call your backend with real authentication. The baseline ships a curated tool set: profile lookup plus admin-gated flag and user-count tools, in src/server/mcp/server.ts. Tools are an explicit allowlist by design; add tools deliberately rather than exposing the whole router.

Health check

/api/health returns 200 with a live database ping, or 503 when the database is unreachable. Wire it into your deployment platform's health checks.