All posts

Keep API Docs in Sync with Code: A CI‑First Workflow

A paste-in CI pipeline, repo layout, and versioning policy to keep API docs and behavior aligned, with concrete commands and checks you can run today.

Make OpenAPI the single contract in your repo

Keep openapi.yaml at the service root, next to /src/*. Add a CODEOWNERS rule for /openapi.*, so every contract edit lands through PR review by the API owner.

0

Define the contract

Treat OpenAPI as the source of truth. Handlers implement the paths, methods, parameters, request bodies, responses, schemas, and examples declared in the spec (source: OpenAPI Specification 3.1.0 — OpenAPI Initiative (2021)). Delete wiki-only endpoints and undocumented query parameters.

0

Make examples executable

Give every path request and response schemas plus realistic examples. Use real field shapes, valid enum values, and plausible error payloads; these examples become contract-test fixtures later.

0

Generate docs from CI output

Build the docs site from the same openapi.yaml artifact produced in CI. Keep /docs/* generated, and avoid manual copy-paste between handlers, Markdown, and docs pages.

💡

Use a small repo layout that keeps the contract, implementation, generated docs, workflow, and ownership rule visible together.

/openapi.yaml
/src/*
/docs/*        # generated
/.github/workflows/api-docs.yml
/CODEOWNERS

Example CODEOWNERS entry: /openapi.* @api-owners.

CI pipeline: 4 checks that block drift before merge

0

Lint and validate

Run the Spectral lint command in CI against openapi.yaml and .spectral.yaml. Treat Spectral errors as merge blockers; the CLI is designed to lint OpenAPI documents against a ruleset (source: Stoplight Spectral docs (consulted 2026-06)).

0

Run contract tests

Start the dev server, wait until it is reachable, then run Dredd against openapi.yaml and the local service URL. Dredd is used to compare documented API behavior with HTTP responses from the service (source: Dredd documentation (consulted 2026-06)).

0

Detect breaking spec changes

Compare openapi.yaml in the pull request against origin/main. Fail the job when a path or response field disappears without a version bump, and label the result as non-backward compatible.

0

Smoke-test docs generation

Build the docs artifact from openapi.yaml, then run the link checker used by the docs site. Fail on generator errors or broken links, because both mean the published contract cannot be trusted.

Use separate GitHub Actions jobs so branch protection can require each check by name. pull_request workflows run on PR activity, and required status checks can block merging until selected jobs pass (source: GitHub Actions Documentation (consulted 2026-06)).

name: api-contract
on: [pull_request]

jobs:
  spectral:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npx @stoplight/spectral lint openapi.yaml --ruleset .spectral.yaml

  dredd:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run dev &
      - run: npx dredd openapi.yaml http://127.0.0.1:3000

  spec-diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - run: npm run spec:diff -- openapi.yaml origin/main:openapi.yaml

  docs-build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run docs:build && npm run docs:check-links

Versioning and deprecation in the spec (not the wiki)

info.version is the API contract version, not the docs site version. In openapi.yaml, bump it only when clients must change code: removed operation, renamed field, stricter required input, or changed response shape. OpenAPI defines info.version as required Info Object metadata (source: OpenAPI Specification 3.1.0 — OpenAPI Initiative (2021)).

DecisionSpec location
Path-based versioning`servers` and `paths`, for example `/v1` on every route
Header-based versioningA required `Accept` or `Version` header parameter on every versioned operation
Deprecated operation or field`deprecated: true` plus a removal date in `description`

Set deprecated: true in the spec before merging code that emits the old shape. Put the removal date in the same description, so generated docs and SDK warnings carry the deadline without a wiki lookup.

💡

A deprecation PR should update the relevant parts of openapi.yaml: deprecated: true, a removal-date sentence, a changelog note in info.description, and a response header such as Deprecation under responses.*.headers for runtime visibility.

Pick either path-based or header-based versioning once. Do not mix /v1 routes with header-only versioning unless the spec models both consistently in servers, paths, parameters, and responses.

Examples, SDKs, and docs from one spec artifact

Treat openapi.yaml as the build input for consumer-facing artifacts: executable examples, SDK packages, and the static docs site.

0

Test examples against the service

Keep request and response examples realistic: use valid IDs, enum values, error bodies, and pagination shapes that your service can return. Run Dredd in CI against the deployed test service; it compares the API description with real HTTP responses and helps catch drift in documented behavior (source: Dredd documentation (consulted 2026-06)).

0

Generate SDKs, then compile them

Generate SDKs from the same file with openapi-generator-cli generate -i openapi.yaml -g typescript-fetch -o build/sdk/typescript. Then run the SDK compiler or package test command as a smoke test, so invalid schemas, renamed fields, and incompatible operation signatures fail before merge (source: OpenAPI Generator docs (consulted 2026-06)).

0

Build docs from the tested spec

Publish the docs site from the tested openapi.yaml. Do not copy endpoint descriptions into a CMS page or README fork.

0

Version and attach release artifacts

On release, tag the spec commit, then attach generated SDK packages and the docs build output to that release. Consumers can trace every SDK and docs page back to the contract that produced it.

Rollout checklist and a paste‑in template pipeline

0

Seed the repo

Commit openapi.yaml, .spectral.yaml, and .github/CODEOWNERS. Put /openapi.* under API owner review. Keep the Spectral ruleset minimal so the first rollout finds real drift, not style noise (source: Stoplight Spectral docs (consulted 2026-06)).

0

Start soft, then enforce

Run the CI job as non-blocking for the first rollout with continue-on-error. After flaky checks have owners and fixes, remove that flag and mark the job required in branch protection (source: GitHub Actions Documentation (consulted 2026-06)).

0

Backfill examples

Use access logs to choose high-traffic endpoints. Add request and response examples for those paths, then extend coverage weekly until every critical flow has examples.

0

Remove duplicate docs

Point docs hosting at the CI-built artifact. Delete wiki pages that duplicate paths, parameters, responses, or examples already present in openapi.yaml.

Add this standing PR-template line: If this changes the API, update openapi.yaml and examples.

# .github/workflows/openapi.yml
name: openapi
on: [pull_request]

jobs:
  contract:
    runs-on: ubuntu-latest
    continue-on-error: true
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx spectral lint openapi.yaml
      - run: npm run openapi:diff
      - run: npm run contract:test
      - run: npm run docs:build
      - uses: actions/upload-artifact@v4
        with:
          name: openapi-docs
          path: dist/docs

Continue reading