skills/api-documentation-generator/SKILL.md
Generates OpenAPI 3.0/3.1 specs, TypeScript client types, and curl examples from existing route handler code. Supports Express, Next.js API routes, Fastify, and Hono. Produces complete documentation including auth requirements, rate limits, error codes, and request/response examples. Activate on: 'generate API docs', 'OpenAPI from code', 'document endpoints', 'API reference', 'swagger generation', 'endpoint documentation'. NOT for: API design from scratch (use api-architect), deployment (use devops-automator), testing (use test-automation-expert).
npx skillsauth add curiositech/windags-skills api-documentation-generatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Extracts API documentation from existing route handler code. Reads your source, infers schemas, and produces OpenAPI specs, TypeScript types, and runnable curl examples -- not from design intent but from actual implementation.
Activate on: "generate API docs", "OpenAPI from code", "document endpoints", "API reference", "swagger generation", "endpoint documentation", "create API types", "curl examples for API"
NOT for: API design from scratch --> api-architect | OpenAPI authoring without source code --> openapi-spec-writer | API testing --> test-automation-expert
Before generating anything, identify the framework. The extraction strategy differs significantly.
Signals: app.get(), router.post(), express.Router()
Route source: app._router.stack or explicit router files
Middleware chain: app.use() order matters for auth detection
Look for route registration patterns:
router.get('/users/:id', authenticate, getUser) -- middleware before handler means auth requiredapp.use('/api/v1', rateLimiter({ max: 100 }), apiRouter) -- rate limiter applied to subtreeSignals: route.ts files exporting GET, POST, PUT, DELETE, PATCH
Route source: File-system convention in app/api/
Middleware chain: middleware.ts at directory boundaries
Path parameters come from folder names: app/api/users/[id]/route.ts --> /api/users/{id}
Signals: pages/api/**/*.ts with default export handler
Route source: File-system convention
Method routing: req.method switch inside handler
Signals: fastify.get(), fastify.route(), schema property on route options
Route source: Plugin registration with fastify.register()
Schema: Fastify routes often have JSON Schema already -- extract and convert
Fastify is the easiest framework to document because routes frequently declare their own schemas. Prioritize extracting existing schema.body, schema.response, and schema.querystring before inferring.
Signals: app.get(), app.post(), Hono(), zValidator()
Route source: Method chaining on Hono instance
Validation: zod-based via zValidator middleware
Hono with zValidator gives you Zod schemas directly. Convert Zod --> JSON Schema --> OpenAPI schema.
Scan the project for route registration. Do not rely on a single entry point -- frameworks often split routes across files.
# Express: find router files
grep -rl "express.Router\|app.get\|app.post\|router\." src/ --include="*.ts" --include="*.js"
# Next.js App Router: find route handlers
find src/app/api -name "route.ts" -o -name "route.js"
# Next.js Pages: find API routes
find src/pages/api -name "*.ts" -o -name "*.js"
# Fastify: find route registrations
grep -rl "fastify\.\(get\|post\|put\|delete\|route\)" src/ --include="*.ts"
# Hono: find route definitions
grep -rl "app\.\(get\|post\|put\|delete\|patch\)" src/ --include="*.ts"
For each route, identify the request and response shapes.
Priority order for schema sources:
When using runtime inference (option 5), always mark the schema with x-inferred: true in the OpenAPI output so consumers know it may be incomplete.
Walk the middleware chain for each route to determine auth requirements:
security: [] (public)security: [{ bearerAuth: [] }]security: [{ apiKeyAuth: [] }]security: [{ cookieAuth: [] }]Read each handler and list every error response:
res.status(400).json(...) or throw new BadRequestError(...) --> 400 response schemares.status(401) --> 401 (include if auth middleware present even without explicit throw)res.status(404) --> 404res.status(409) --> 409 (conflict, common in create operations)res.status(422) --> 422 (validation failure)res.status(429) --> 429 (rate limited, include if rate limiter middleware detected)res.status(500) --> 500 (always include as possibility)Produce three artifacts:
.d.ts file with all request/response types)If handlers accept raw req.body without Zod/Joi validation, you must infer types from usage. Look for:
req.body.name, req.body.email --> { name: string, email: string }const { name, email } = req.body --> same inferencedb.users.create({ name: body.name }) --> cross-reference DB schema if availableFlag the entire spec with a warning: x-schemas-inferred: true at the info level.
:id --> OpenAPI {id}[id] --> OpenAPI {id}[...slug] --> OpenAPI {slug} with note about array behavior:id --> OpenAPI {id}:id --> OpenAPI {id}Document each status code with its own schema. Do not merge 200 and 201 into one schema if they differ. Common pattern:
responses:
'200':
description: User found
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: User not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Document rate limits using OpenAPI extensions:
x-rate-limit:
requests: 100
window: 60s
scope: per-api-key
If the rate limiter is global (applied once at the app level), document it at the top-level info section. If per-route, document on each operation.
Generate types that match the OpenAPI spec exactly. Use branded types for IDs when the codebase uses them.
// Generated from OpenAPI spec -- do not edit manually
export interface CreateUserRequest {
name: string;
email: string;
role?: 'admin' | 'member' | 'viewer';
}
export interface CreateUserResponse {
id: string;
name: string;
email: string;
role: 'admin' | 'member' | 'viewer';
createdAt: string; // ISO 8601
}
export interface ErrorResponse {
error: {
code: string;
message: string;
details?: Array<{
field: string;
issue: string;
}>;
};
}
export interface PaginatedResponse<T> {
data: T[];
meta: {
page: number;
perPage: number;
total: number;
hasMore: boolean;
};
}
Generate one curl per endpoint. Use realistic but obviously fake data.
# GET /api/users/:id -- Fetch a single user
curl -X GET https://api.example.com/api/users/usr_abc123 \
-H "Authorization: Bearer sk_test_..." \
-H "Accept: application/json"
# POST /api/users -- Create a new user
curl -X POST https://api.example.com/api/users \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Ada Lovelace",
"email": "[email protected]",
"role": "member"
}'
# GET /api/users -- List users with pagination
curl -X GET "https://api.example.com/api/users?page=1&perPage=20&role=admin" \
-H "Authorization: Bearer sk_test_..." \
-H "Accept: application/json"
Rules for curl examples:
https://api.example.com as base)-d with formatted JSON for request bodiessk_test_... for auth tokens (obviously fake)Symptom: Spec describes endpoints that do not exist in code yet Fix: Only document what the code actually implements. If a planned endpoint is in comments or a spec file but not in handlers, exclude it.
Symptom: Docs say endpoint is public when it actually requires auth through a parent middleware
Fix: Trace the full middleware chain from app root to handler. A router.use(auth) above the route definition means all routes below require auth.
Symptom: Documenting req.body as any or object because there is no validation
Fix: Infer from usage (property access, destructuring, database calls). Mark as x-inferred: true. Recommend adding Zod validation.
Symptom: Every endpoint lists the same generic "400 Bad Request" without details Fix: Read each handler's error paths. Document the specific error codes and messages returned. Different validation failures should show different example responses.
Symptom: Spec was generated once and never updated as code changed
Fix: Generate into a well-known path (docs/openapi.yaml). Add a CI check that regenerates and diffs against committed spec. If they diverge, fail the build.
Symptom: List endpoints documented without query parameter schemas for pagination
Fix: If the handler supports page, limit, cursor, offset, or similar parameters, document them with defaults and max values.
Symptom: Multipart/form-data endpoints documented as JSON
Fix: Detect multer, formidable, busboy, or framework-native file handling. Use multipart/form-data content type with proper binary schema.
[ ] Every route handler in the codebase has a corresponding OpenAPI operation
[ ] Path parameters match between code and spec (no :id vs {userId} mismatches)
[ ] Request body schemas cover all required and optional fields
[ ] Response schemas match actual JSON structure (verified by reading handler return)
[ ] Authentication requirements match middleware chain analysis
[ ] Rate limit information documented where rate limiting middleware exists
[ ] Error responses cataloged from actual throw/return statements in handlers
[ ] Curl examples execute successfully against a running instance
[ ] TypeScript types compile without errors
[ ] No x-inferred schemas left undocumented (each flagged one has a TODO for proper validation)
[ ] Pagination parameters documented with defaults and maximums
[ ] File upload endpoints use multipart/form-data content type
[ ] OpenAPI spec validates with spectral or swagger-cli lint
[ ] Generated spec committed to a known path for CI diffing
docs/openapi.yaml -- Complete OpenAPI 3.0/3.1 specificationdocs/api-types.d.ts -- TypeScript type definitions for all request/response shapesdocs/api-examples.sh -- Runnable curl examples for every endpointdocs/api-coverage.md -- Report listing documented vs undocumented routes# Lint the generated OpenAPI spec
npx @stoplight/spectral-cli lint docs/openapi.yaml
# Validate spec structure
npx swagger-cli validate docs/openapi.yaml
# Generate TypeScript client to verify types compile
npx openapi-typescript docs/openapi.yaml -o /tmp/api-check.d.ts
tsc --noEmit /tmp/api-check.d.ts
# Diff against last committed spec (CI check)
diff <(cat docs/openapi.yaml) <(npx tsx scripts/generate-api-docs.ts --stdout) && echo "Spec is current" || echo "Spec is stale"
Covers: OpenAPI generation | TypeScript type extraction | Curl example creation | Auth documentation | Rate limit documentation | Error code cataloging
Use with: api-architect (design first, document after) | openapi-spec-writer (manual authoring) | test-automation-expert (contract testing against generated spec)
tools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.