Backend
api-documentation-sync
$npx skills add blunotech-dev/agents --skill api-documentation-syncKeep API docs in sync with implementation by generating or validating OpenAPI/Swagger specs, detecting undocumented routes, and fixing spec drift. Use when the user asks to generate docs, validate a spec, find undocumented endpoints, or update stale API documentation.
| name | description | category |
|---|---|---|
| api-documentation-sync | Keep API docs in sync with implementation by generating or validating OpenAPI/Swagger specs, detecting undocumented routes, and fixing spec drift. Use when the user asks to generate docs, validate a spec, find undocumented endpoints, or update stale API documentation. | Backend |
API Documentation Sync
Two modes: Generate (routes → spec) or Validate (spec ↔ routes). Identify which mode applies before proceeding.
Phase 1: Discovery
Route extraction — non-obvious patterns to catch
Most routes are not just app.get(...). Scan for:
- Router mounts:
app.use('/prefix', router)— the prefix compounds. Reconstruct full paths. - Dynamic registrations: routes registered in loops, from config arrays, or via decorators — trace the source data.
- Middleware-as-route: auth middleware on
router.use(...)that also terminates requests (e.g., returns 401) — these are implicit endpoints. - Nested routers: routers mounted on routers. Build a full prefix chain.
- Framework-specific: NestJS
@Controller+@Get/@Postdecorators; FastAPIAPIRouter; Djangourlpatternswithinclude(); Hono.route().
Collect for each route:
- Full path (with all prefix segments resolved)
- HTTP method(s)
- Handler function name/location
- Any inline validation schema (Zod, Pydantic, Joi, Yup, class-validator)
- Auth guard presence (middleware name, decorator)
Schema extraction — non-obvious sources
Don't just look at request body. Extract:
- Path params: named captures in route strings (
:id,{id},<int:pk>) - Query params:
req.query.*accesses or framework query decorators - Response shapes: infer from
res.json(...)calls or explicit return type annotations - Validation schemas: Zod
.parse()/.safeParse(), Pydantic model in function signature, Joi.validate()— these are ground truth for request shape - Error responses: catch blocks that
res.status(4xx).json(...)— document these as response variants
Phase 2: Strategy
Generate mode
- Confirm OpenAPI version target (default: 3.1.0 unless codebase shows 3.0.x or Swagger 2.0)
- Determine output format: YAML (preferred for readability) or JSON
- Ask if existing partial spec exists to merge into — don't overwrite human-authored descriptions
- Identify reusable schemas (same shape used across multiple routes →
$reftocomponents/schemas)
Validate mode
- Parse the existing spec — note the
servers[].urlbase path, it affects path matching - Extract all
pathsentries from spec - Cross-reference against discovered routes using normalized path comparison:
- OpenAPI uses
{param}notation; Express uses:param— normalize before comparing - Ignore trailing slashes unless the framework distinguishes them
- OpenAPI uses
- Classify findings into three buckets:
- Ghost routes: in spec, not in code (deleted or renamed)
- Undocumented routes: in code, not in spec
- Drift: route exists in both but method, params, or response schema diverge
Phase 3: Execution
Generating the spec
Build the spec incrementally:
openapi: 3.1.0
info:
title: <infer from package.json name or ask>
version: <infer from package.json version or "0.1.0">
paths:
/resource/{id}:
get:
summary: <derive from handler name, e.g. getUserById → "Get user by ID">
parameters:
- name: id
in: path
required: true
schema:
type: string # or integer — infer from validation schema or usage
responses:
"200":
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/User'
"404":
description: Not found
Non-obvious generation rules:
- Handler name →
operationId: camelCase handler name maps directly.getUserById→operationId: getUserById - Auth middleware presence → add
securityfield on that operation (useBearerAuthas default scheme name) - If validation schema exists (Zod/Pydantic/Joi), derive the JSON Schema directly from it — don't guess
- For routes with no response type info, use
description: Successwith no schema rather than fabricating a shape - Group routes under shared tags by their first path segment (
/users/*→ tag:users)
Validating the spec
Output a structured diff report:
GHOST ROUTES (in spec, not in code):
DELETE /api/v1/users/{id} — handler not found
UNDOCUMENTED ROUTES (in code, not in spec):
POST /api/v1/auth/refresh
GET /api/v1/admin/stats
DRIFT DETECTED:
GET /api/v1/users/{id}
spec: response 200 → schema $ref User
code: handler returns { user, meta } — schema mismatch
POST /api/v1/orders
spec: body requires { productId, quantity }
code: Zod schema requires { productId, quantity, shippingAddressId }
For each drift item, suggest the fix (update spec or update code) based on which is more likely to be ground truth.
Phase 4: Output
Generate mode output
- Single
openapi.yaml(or.json) at project root ordocs/— ask if no convention is visible - If a partial spec existed, merge: preserve existing
description,summary,examplefields; updateparameters,requestBody,responsesfrom code
Validate mode output
- Report in the format above, grouped by severity
- Offer to: (a) auto-add undocumented routes to spec as stubs, (b) remove ghost routes, (c) flag drift items for manual review
- Never silently mutate the spec — always confirm before writing
Edge cases to handle
- Versioned APIs (
/v1/,/v2/): treat as separate tag groups, not separate specs unless user requests it - File upload routes:
multipart/form-datacontent type — set explicitly, notapplication/json - Streaming endpoints (SSE, chunked): note in spec as
text/event-stream, flag that schema coverage is limited - Health/internal routes (
/health,/metrics,/__internal): ask if these should be excluded from public spec - Optional auth: some routes accept both authenticated and unauthenticated requests with different responses — document both response variants