API documentation that drifts from the actual code is worse than no docs. Claude Code can generate and maintain OpenAPI specs from your TypeScript types and Zod schemas — keeping docs always in sync.
CLAUDE.md for API Documentation
## API Documentation Rules
### OpenAPI Standard
- Version: OpenAPI 3.1
- Output: docs/openapi.yaml
- Code-first only: auto-generate from code (no hand-written YAML)
### Code Annotations
- TypeScript types become schemas
- JSDoc comments provide descriptions
- @example tags provide examples
### Endpoint Requirements
- All endpoints need summary and description
- All responses typed (200/400/401/403/500)
- Pagination format: { data, total, page, limit }
### Tools
- zod-to-openapi (Zod schemas → OpenAPI)
- swagger-ui-express (Swagger UI for dev)
Generating the zod-to-openapi Setup
Set up zod-to-openapi to auto-generate OpenAPI 3.1 spec from Zod schemas.
Existing schemas:
- CreateUserSchema: { email: string, name: string, role: 'admin' | 'user' }
- UserSchema: { id: string, email: string, name: string, role: string, createdAt: string }
Requirements:
- OpenAPI 3.1
- description and example on all fields
- JWT Bearer authentication
- Standard error response: { error: string, details?: object }
Generate: src/docs/schemas.ts → docs/openapi.yaml
Generated Schema with Annotations
// src/docs/schemas.ts
import { extendZodWithOpenApi } from 'zod-to-openapi';
import { z } from 'zod';
extendZodWithOpenApi(z);
export const CreateUserSchema = z
.object({
email: z
.string()
.email()
.openapi({ description: "User's email address", example: 'user@example.com' }),
name: z
.string()
.min(1)
.max(100)
.openapi({ description: 'Display name', example: 'John Doe' }),
role: z
.enum(['admin', 'user'])
.openapi({ description: 'User role', example: 'user' }),
})
.openapi('CreateUserRequest');
export const UserSchema = z
.object({
id: z.string().uuid().openapi({ description: 'User ID' }),
email: z.string().email(),
name: z.string(),
role: z.enum(['admin', 'user']),
createdAt: z.string().datetime(),
})
.openapi('User');
Generating Endpoint Annotations
Add OpenAPI annotations to these Express endpoints:
- POST /users (create user)
- GET /users (paginated list)
- GET /users/:id (get user)
- PATCH /users/:id (partial update)
- DELETE /users/:id (soft delete)
For each endpoint add:
- summary, description, tags
- requestBody (referencing Zod schemas)
- responses (200/201/400/401/403/404/500 — ALL of them)
- security (for JWT-protected endpoints)
Integrating Swagger UI
Add Swagger UI to the Express app.
Requirements:
- Path: /api/docs
- Disabled in production (NODE_ENV=production)
- Dark theme
- Persist authorization (so API key stays set between page refreshes)
Packages: swagger-ui-express, js-yaml
import swaggerUi from 'swagger-ui-express';
import YAML from 'js-yaml';
import fs from 'fs';
if (process.env.NODE_ENV !== 'production') {
const openapiSpec = YAML.load(
fs.readFileSync('./docs/openapi.yaml', 'utf8')
) as object;
app.use(
'/api/docs',
swaggerUi.serve,
swaggerUi.setup(openapiSpec, {
customCss: '.swagger-ui .topbar { display: none }',
swaggerOptions: { persistAuthorization: true },
})
);
}
Enforcing Sync in CI
Add a CI step that fails if the OpenAPI spec is out of date.
Requirements:
- Regenerate OpenAPI during build
- Compare with committed docs/openapi.yaml
- Fail if diff detected
- Error message: "Run `npm run generate:openapi` and commit the changes"
This makes it impossible to merge code without updating the docs.
Summary
Keep OpenAPI docs always in sync with Claude Code:
- CLAUDE.md — Define code-first standard, zod-to-openapi as tool
- Annotated Zod schemas — descriptions, examples, deprecation flags
- Full response coverage — 400/401/403/404/500, not just happy path
- Swagger UI — Dev-only, with auth persistence
- CI enforcement — Docs drift = failed build
Code Review Pack (¥980) includes /code-review for API documentation review — missing responses, unannotated schemas, inconsistent formats.
Myouga (@myougatheaxo) — Claude Code engineer focused on production API design.
Top comments (0)