OpenAPI Auto-Generation: Turn Your Code into Perfect API Docs
Stop hand-writing API documentation that drifts out of sync the moment you ship. Here is how to auto-generate OpenAPI specs directly from your code.
DocPilot Team
AI documentation research and engineering from Onzira Labs.
Your API documentation is wrong. Not conceptually wrong, but factually wrong — the endpoint you shipped last Tuesday already accepts two new query parameters, the response schema gained an optional field, and the example payload still shows the old shape. Hand-written docs decay within days of the first commit, and every developer who has ever maintained a public API knows it.
OpenAPI auto-generation solves this by treating your code as the source of truth. Instead of writing YAML by hand and hoping engineers remember to update it, you generate the spec from annotations, type signatures, or runtime inspection. The result is documentation that cannot drift further than your last build.
This guide walks through the practical mechanics: which approach fits which stack, how to set up generation in a CI pipeline, and how to keep the output clean enough to publish.
What "Auto-Generation" Actually Means
The OpenAPI Initiative defines a single specification format (currently 3.1.0) for describing REST APIs. Auto-generation refers to any tooling that emits a valid OpenAPI document by reading your source code or runtime behavior, rather than by having a human author YAML.
Three generation strategies dominate:
| Strategy | Source of Truth | Best For | Trade-offs |
|---|---|---|---|
| Annotation-based | Decorators/attributes in route handlers | TypeScript, Python, Java, C# | Requires code changes; very rich metadata |
| Schema-derived | Type definitions and route registration | Go, Rust, TypeScript | Light-touch but loses semantic detail |
| Runtime introspection | Live HTTP traffic or framework hooks | Any language | Captures reality; harder to add descriptions |
Most production setups combine two. A Node.js service might use a schema-derived approach for the bulk of endpoints and add annotations only where the documentation needs richer prose.
Annotation-Based Generation in Practice
The most popular path for new projects is annotation-based, because it puts human-readable context right next to the code it describes.
TypeScript with zod-to-openapi
If you already validate request bodies with Zod, you can register each schema with a helper and produce a complete spec:
import { z } from 'zod';
import { extendZodWithOpenApi, generateDocument } from '@asteasolutions/zod-to-openapi';
extendZodWithOpenApi(z);
const UserSchema = z.object({
id: z.string().uuid().openapi({ description: 'Server-issued UUID' }),
email: z.string().email().openapi({ example: 'a@b.com' }),
createdAt: z.string().datetime().openapi({ description: 'ISO 8601 timestamp' }),
}).openapi('User');
const document = generateDocument({
openapi: '3.1.0',
info: { title: 'Users API', version: '1.0.0' },
paths: {
'/users/{id}': {
get: {
requestParams: {
path: z.object({ id: z.string().uuid() }),
},
responses: {
200: {
description: 'Fetch a user',
content: { 'application/json': { schema: UserSchema } },
},
},
},
},
},
});
The benefit is structural: because the same Zod schema validates incoming requests and shapes outgoing responses, the spec cannot disagree with runtime behavior.
Python with FastAPI
FastAPI remains the cleanest demonstration of the model. Decorators carry the full description:
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI(title="Orders API", version="2.0.0")
class Order(BaseModel):
id: str = Field(..., description="Server-issued order ID")
total_cents: int = Field(..., ge=0, description="Amount in cents, never floats")
@app.get("/orders/{id}", summary="Fetch an order", response_model=Order)
def get_order(id: str):
...
FastAPI exposes the generated spec at /openapi.json. The framework inspects type hints, Pydantic validators, Field metadata, and docstrings, then assembles a valid 3.1 document on every request. There is no separate build step.
Go with oapi-codegen
Go takes the inverse path: write the spec first, then generate server interfaces from it. This is "code from spec" rather than "spec from code," but it belongs in the same workflow. Teams that publish a stable contract to external consumers often prefer this model because partners can review the YAML before any implementation exists.
# openapi.yaml
oapi-codegen generates:
types.go: Models
server.go: HTTP interface
Schema-Derived Generation
When you want minimal intrusion, schema-derived tools read your type system and route registration directly.
Hono with hono-openapi
Hono is small enough that the spec generator can introspect the chain:
import { OpenAPIHono } from '@hono/zod-openapi';
const app = new OpenAPIHono();
app.openapi(
createRoute({
method: 'get',
path: '/health',
responses: {
200: { description: 'Liveness probe', content: { 'application/json': { schema: HealthSchema } } },
},
}),
(c) => c.json({ status: 'ok' })
);
app.doc('/openapi.json', { openapi: '3.1.0', info: { title: 'Health API', version: '1.0.0' } });
The route definition is the documentation. Adding a new endpoint is a single PR; the spec follows.
NestJS with @nestjs/swagger
NestJS combines both models. Class decorators (@ApiProperty) provide field-level metadata, while CLI plugins can derive response schemas from TypeScript classes without explicit decorators. The plugin approach is especially useful in legacy codebases where adding decorators to every DTO would be a multi-week project.
Runtime Introspection
Annotation and schema tools can miss anything that lives outside the route handler — middleware, request transforms, conditional headers. Runtime introspection tools like tsoa, express-openapi-validator, or proxy-based tools like Optic observe actual HTTP traffic and reconstruct the spec from observed shapes.
This is the only strategy that captures behavior you did not document. The downside is that undocumented behavior is often undocumented for a reason: it is accidental, and codifying it as a contract can lock in bugs. Use runtime tools to discover gaps, then promote the meaningful ones into explicit schemas.
Wiring Generation Into CI
Auto-generation only pays off if the output is checked in, reviewed, and published on every merge. A minimal pipeline:
# .github/workflows/docs.yml
name: API Docs
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm run openapi:build # writes ./dist/openapi.json
- uses: actions/upload-artifact@v4
with:
name: openapi-spec
path: dist/openapi.json
- run: npx @redocly/cli lint dist/openapi.json
The lint step is the unsung hero. Spectral or Redocly CLI catches missing descriptions, inconsistent error schemas, and accidentally exposed internal endpoints before they reach a public docs portal.
Common Pitfalls
After several codebases of OpenAPI work, the same mistakes repeat:
- Polling the endpoint for the spec at runtime in production. This couples every docs request to application latency. Build the spec at deploy time and serve it as a static file.
- Allowing free-form descriptions. A spec without enforced field descriptions produces docs that look professional and say nothing. Treat descriptions as required fields in lint rules.
- Mixing versions. Pin a single OpenAPI version per spec. 3.0 and 3.1 have different JSON Schema semantics, and tooling that supports both will produce subtle drift.
- Forgetting auth metadata. The
securitySchemessection is where you describe OAuth flows, API keys, and bearer tokens. Without it, consumers cannot call protected endpoints from the docs UI.
Keeping Docs Honest
Generation prevents drift between code and spec. It does not prevent drift between spec and reality. Three habits close the loop:
- Run contract tests against the published spec. Tools like
schemathesisgenerate adversarial requests from the schema and compare responses to declared shapes. - Review spec diffs in pull requests the same way you review code diffs. A 200-line schema change is not automatically correct.
- Version your spec alongside your code. Breaking changes deserve a new major version, an updated server URL, and a migration guide.
If you maintain a GitHub repository and would rather not wire all of this up by hand, DocPilot connects to your repos, detects your framework's OpenAPI output (or generates one from your route definitions), and keeps a README, setup guide, and full API reference in sync through webhooks. It handles the build, lint, and publish steps so your team can focus on shipping endpoints rather than describing them. The free tier covers public repositories and is worth a few minutes to evaluate against whatever pipeline you have today.
Read next
api-documentation
The Underrated Power of Well-Structured API Docs
Most API docs fail not because of missing information, but because of missing structure. Here's why architecture beats aesthetics every time.
Read
ai-chat
How to Add AI Chat to Your Product Documentation in an Afternoon
Your users are searching your docs with the wrong keywords. Here's how to ship an AI chat layer over them before lunch.
Read
migration
Migrate to AI Documentation in 30 Days: A Complete Plan
Most documentation migrations die in week three. Here is the 30-day plan that survives contact with reality — week-by-week tasks, scripts, and rollout tactics that turn AI docs from a side project into your team's default.
Read