API Docs
Published Oct 8, 2025 | Updated Mar 13, 2026
Add OpenAPI + Scalar API documentation to your backend
Overview
Add production-ready OpenAPI generation and interactive API docs to your backend.
Install
npx zuro add docsScaffolds OpenAPI + Scalar routes and mounts API docs under /api/docs.
What This Generates
src/
├ lib/
│ └ openapi.ts
├ routes/
│ └ docs.routes.ts
└ routes/index.ts # updated to mount /docssrc/lib/openapi.ts: OpenAPI registry + document generator.src/routes/docs.routes.ts: serves JSON spec and Scalar UI.src/routes/index.ts: mounts docs routes at/api/docs.
Quick Example
npx zuro add docs
npm run devExample request:
GET /api/docs/openapi.jsonExample response:
{
"openapi": "3.0.3",
"info": {
"title": "Zuro API",
"version": "1.0.0"
},
"paths": {
"/health": {
"get": {}
}
}
}Open interactive docs at http://localhost:3000/api/docs.
How It Works
- Client opens
/api/docs. - Scalar UI loads and requests
/api/docs/openapi.json. createOpenApiDocument()builds the spec from the OpenAPI registry.- Spec is returned and rendered in the docs UI.
Configuration
# No required environment variablesdocsis code-configured insrc/lib/openapi.ts.
API Reference
GET /api/docs: interactive Scalar docs UI.GET /api/docs/openapi.json: generated OpenAPI spec.createOpenApiDocument(): exports the OpenAPI document builder.
Advanced Usage
Register custom endpoints in src/lib/openapi.ts:
import { z } from "zod";
const createPostSchema = z.object({
title: z.string().min(1),
content: z.string().min(1),
});
registry.registerPath({
method: "post",
path: "/api/posts",
tags: ["Posts"],
request: {
body: {
content: {
"application/json": {
schema: createPostSchema,
},
},
},
},
responses: {
201: { description: "Post created" },
},
});Integrate module docs automatically:
npx zuro add docs
npx zuro add auth
npx zuro add uploadsWhen installed together, auth/uploads endpoints are injected into openapi.ts.
Example Use Cases
- Internal API reference for backend teams.
- Client SDK generation from OpenAPI JSON.
- QA/UAT testing with interactive endpoint docs.
- Publishing auth and upload workflows in one API portal.
Registering Your Routes
All endpoints need to be registered in src/lib/openapi.ts to appear in the spec. Use registry.registerPath() with Zod schemas for request/response shapes:
import { z } from "zod";
import { registry } from "./openapi";
// Register a GET endpoint
registry.registerPath({
method: "get",
path: "/api/users/{id}",
tags: ["Users"],
request: {
params: z.object({ id: z.string().uuid() }),
},
responses: {
200: {
description: "User found",
content: {
"application/json": {
schema: z.object({
id: z.string(),
email: z.string().email(),
name: z.string().nullable(),
}),
},
},
},
404: { description: "User not found" },
},
});Register a POST endpoint with a request body:
const createUserBody = z.object({
email: z.string().email(),
name: z.string().min(1),
password: z.string().min(8),
});
registry.registerPath({
method: "post",
path: "/api/users",
tags: ["Users"],
request: {
body: {
content: {
"application/json": { schema: createUserBody },
},
},
},
responses: {
201: { description: "User created" },
422: { description: "Validation error" },
},
});Adding Authentication to Docs
If your API uses Bearer token auth (JWT), register a security scheme so the Scalar UI shows an Authorize button:
// src/lib/openapi.ts
registry.registerComponent("securitySchemes", "bearerAuth", {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
});Then apply it to protected routes:
registry.registerPath({
method: "get",
path: "/api/users/me",
tags: ["Users"],
security: [{ bearerAuth: [] }],
responses: {
200: { description: "Current user profile" },
401: { description: "Unauthorized" },
},
});The Scalar UI will show a lock icon on secured routes and an Authorize dialog for entering tokens.
Generating a Client SDK
With the OpenAPI spec available at /api/docs/openapi.json, you can auto-generate a typed client:
# Using openapi-typescript
npx openapi-typescript http://localhost:3000/api/docs/openapi.json -o src/types/api.d.tsOr generate a full fetch client:
# Using hey-api
npx @hey-api/openapi-ts \
--input http://localhost:3000/api/docs/openapi.json \
--output src/client \
--client fetchTroubleshooting
Routes not appearing in the spec
You must call registry.registerPath() in src/lib/openapi.ts — simply creating Express routes does not auto-populate the spec. Zuro injects auth and upload routes automatically when those modules are installed together.
/api/docs returns 404 after adding the module
Ensure the docs routes are mounted. Check src/routes/index.ts for:
import docsRouter from "./docs.routes";
router.use("/docs", docsRouter);Zuro injects this automatically, but manual route files may need it added.
Scalar UI blank / not loading
Scalar loads its UI assets from a CDN. In environments without internet access (air-gapped, some CI), the UI will be blank. The JSON spec at /api/docs/openapi.json still works and can be imported into Postman or Insomnia.
Schema too large / slow to generate
If you have hundreds of registered paths, move createOpenApiDocument() to a cached singleton so it's only built once per server start rather than on every request.