Published on

Documentation as Code — Keeping Your API Docs Accurate and Always Up to Date

Authors

Introduction

Documentation rots. You know this. You update the API, forget to update the docs, and three weeks later someone is reading stale information. The answer isn't "writers need to be more diligent." The answer is architecture: keep docs close to code so they can't drift apart.

Documentation-as-code means docs live in your repo, are reviewed like code, and are validated in CI. When code changes, docs change with it or the build fails.

Why Docs Rot

Docs rot because:

  1. They're written separately from code
  2. Nobody validates them in CI
  3. Code changes, docs don't
  4. Docs are treated as optional

The fix: treat docs like code. Commit them with changes. Review them in PRs. Validate them in CI. Then they can't fall behind.

Inline JSDoc → OpenAPI Generation

Start with JSDoc in your code. Generate OpenAPI from it.

Example:

/**
 * Get a user by ID
 * @param id - User ID (UUID format)
 * @returns User object with profile and stats
 * @throws 404 if user not found
 */
export async function getUser(id: string): Promise<User> {
  return db.users.findById(id);
}

Tool like tsdoc-openapi can extract this and generate an OpenAPI endpoint definition.

Advantages:

  • Documentation lives in code
  • Changes together with implementation
  • Can't forget to update docs (they're part of the function)
  • IDE shows docs on hover

Workflow:

  1. Write JSDoc comments
  2. Generate OpenAPI on build
  3. Generate client SDKs
  4. Docs stay in sync

TypeDoc for TypeScript API Docs

TypeDoc extracts documentation from TypeScript code and generates an interactive HTML site.

Usage:

npm install --save-dev typedoc
typedoc --out docs src/

Output: A browsable documentation site showing:

  • All exported functions, classes, interfaces
  • Parameter descriptions (from JSDoc)
  • Return types
  • Examples (from code)
  • Inheritance hierarchy

Update code + JSDoc, regenerate docs. No separate docs to maintain.

ADRs (Architecture Decision Records) in Code Repo

ADRs are lightweight architecture documents. Store them in your repo.

Location: /docs/adr/ in your repo

Format (example):

# ADR-001: Use PostgreSQL for primary data store

Date: 2026-03-01
Status: Accepted

## Context
We needed a primary data store for user data.

## Decision
We chose PostgreSQL because:
- ACID transactions
- JSON support
- Strong community
- Mature tooling

## Consequences
- Must manage backups
- Requires DBA knowledge for optimization

Benefits:

  • Decision rationale is documented
  • Future engineers understand why
  • Changes to decisions are version-controlled
  • Can review old decisions to understand evolution

Runbooks as Code

Operational runbooks should be executable where possible.

Example (Markdown with code blocks):

# Incident: High Error Rate in Auth Service

## Detection
- Alert: error_rate &lt; 5% for 5 minutes
- Check dashboard: /dashboards/auth-service

## Investigation
1. Check logs:
   \`\`\`bash
   kubectl logs -l app=auth-service --tail=100
   \`\`\`
2. Check metrics:
   \`\`\`bash
   curl http://prometheus:9090/api/v1/query?query=auth_error_rate
   \`\`\`

## Resolution
If database connection pool exhausted:
1. Restart service:
   \`\`\`bash
   kubectl rollout restart deployment/auth-service
   \`\`\`
2. Monitor recovery
3. Post-mortem

Store in /docs/runbooks/. Copy-paste commands directly into terminal. No mismatches between documented procedure and actual commands.

Auto-Generating Changelogs from Conventional Commits

Conventional commits enable automatic changelog generation.

Conventional commit format:

feat(auth): add two-factor authentication
fix(api): handle null user ID in getUser
docs(api): update endpoint descriptions

Tool: standard-version or conventional-changelog

conventional-changelog -p angular -i CHANGELOG.md -s

Output: CHANGELOG.md with sections:

  • Features
  • Bug Fixes
  • Breaking Changes

Regenerate on each release. Changelog is always accurate and never out of sync.

Mintlify, Scalar, and Redocly for Beautiful API Docs

These tools generate beautiful API documentation from OpenAPI specs.

Mintlify:

  • Beautiful UI
  • Customizable branding
  • Docs + API ref combined
  • Self-hosted or cloud

Scalar:

  • Modern, interactive
  • Dark mode
  • Request/response examples
  • Try-it functionality

Redocly:

  • Enterprise-grade
  • Versioning
  • Analytics
  • Custom portals

All are generated from your OpenAPI spec. Update spec, regenerate. Docs always match.

Testing That Docs Examples Actually Work

Documentation examples should be tested.

Approach 1: Doctests

/**
 * Get a user by ID
 * @example
 * const user = await getUser("550e8400-e29b-41d4-a716-446655440000");
 * console.assert(user.email === "alice@example.com");
 */

Approach 2: Integration tests

it("documentation example: fetch user", async () => {
  const user = await getUser("550e8400-e29b-41d4-a716-446655440000");
  expect(user.email).toBe("alice@example.com");
});

Approach 3: Snapshot tests

# Example API response is captured
GET /api/users/123 →
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "alice@example.com"
}

If API changes, snapshot test fails and docs must be updated.

Docs CI Pipeline

Your CI should validate docs:

# .github/workflows/docs.yml
name: Documentation

on: [pull_request, push]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm install -d
      - run: npm run docs:lint  # markdownlint, etc.

  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm install -d
      - run: npm run docs:generate  # TypeDoc, OpenAPI, etc.
      - run: npm run docs:build     # Docusaurus, etc.

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm install -d
      - run: npm run test:docs  # Run documentation examples

  deploy:
    needs: [lint, generate, test]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v2
      - run: npm run docs:deploy

On every PR:

  • Markdown is linted
  • Docs are generated
  • Doc examples are tested
  • Docs build successfully

If anything fails, PR can't merge.

Docs-as-Code with Docusaurus

Docusaurus is a docs framework for technical documentation.

Structure:

docs/
  api/
    auth.md
    users.md
  guides/
    getting-started.md
    deployment.md
  blog/
    2026-03-15-new-features.md

Markdown with metadata:

---
title: Authentication
sidebar_position: 1
---

# Authentication

## Overview
...

## API Endpoints
...

Build:

npm run build  # Generates static HTML site
npm run serve  # Serve locally

Deploy: Push built site to Vercel or GitHub Pages.

Advantages:

  • Version control all docs
  • Review docs in PRs
  • Search built-in
  • Versioning (multiple API versions)
  • MDX support (React components in Markdown)

Measuring Doc Quality

Track these metrics:

  • Search abandonment rate: If users search but don't click, docs are missing
  • Page bounce rate: If users land and leave immediately, content is wrong
  • Time on page: If low, docs are unclear
  • Feedback votes: "Was this page helpful?" widget

Use Segment or Mixpanel to track. Use Hotjar for heatmaps.

Example metric: If < 50% of docs pages have < 10s average time, those sections need improvement.

Checklist

  • Keep JSDoc comments in all exported functions
  • Generate OpenAPI spec from code
  • Use TypeDoc to generate API reference
  • Store ADRs in /docs/adr/
  • Store runbooks in /docs/runbooks/
  • Use conventional commits
  • Auto-generate changelog
  • Test documentation examples in CI
  • Lint Markdown in CI
  • Deploy docs on every merge to main
  • Track doc quality metrics
  • Review docs in every code PR

Conclusion

Documentation-as-code means docs live where code lives, are reviewed like code, and are validated in CI. They can't rot because they're not separate. Update code, update docs, merge together. This is how production teams maintain accurate documentation in 2026.