The Migration Guide Problem
Every API version bump has the same sequence:
- Engineers change the OpenAPI spec
- The spec passes review
- The API ships
- Someone remembers: "We need a migration guide for consumers"
- A PM copies breaking changes from the PR description into a Google Doc
- The guide is wrong because the PR description was incomplete
- Consumers file support tickets anyway
The root cause: migration guides are written by humans after the fact, from memory, against a deadline. They miss things. They get the severity wrong. They list breaking changes but don't explain what to do about them.
API Contract Guardian's migrate command solves this by generating the migration guide directly from the spec diff:
api-contract-guardian migrate spec-v1.yaml spec-v2.yaml --output MIGRATION.md
The output is a complete, structured migration guide with severity-ranked sections, before/after values, and numbered action items — generated from the actual spec diff, not from human memory.
Quick Start: Generate Your First Migration Guide
Step 1: Install
# pip
pip install api-contract-guardian
# Homebrew (macOS / Linux)
brew tap Coding-Dev-Tools/tap
brew install api-contract-guardian
# Scoop (Windows)
scoop bucket add Coding-Dev-Tools https://github.com/Coding-Dev-Tools/scoop-bucket
scoop install api-contract-guardian
Step 2: Compare Two Spec Versions
api-contract-guardian migrate openapi-v1.yaml openapi-v2.yaml
Output is a markdown migration guide printed to stdout:
Step 3: Write to a File
api-contract-guardian migrate openapi-v1.yaml openapi-v2.yaml --output MIGRATION.md
# Output: Migration guide written to MIGRATION.md
Three Output Formats
The migrate command supports three output formats for different workflows:
| Format | Flag | Best For |
|---|---|---|
| Markdown | --format markdown (default) |
Human-readable guides, GitHub wikis, documentation sites |
| JSON | --format json |
Programmatic processing, custom rendering, CI dashboards |
| YAML | --format yaml |
Kubernetes-native workflows, config management, pipeline steps |
JSON Output for Custom Processing
api-contract-guardian migrate spec-v1.yaml spec-v2.yaml --format json --output migration.json
The JSON output is structured for programmatic consumption:
{
"from_version": "1.0.0",
"to_version": "2.0.0",
"summary": {
"breaking": 3,
"dangerous": 2,
"non_breaking": 5,
"info": 1
},
"breaking_changes": [
{
"kind": "path_removed",
"path": "/api/v1/users",
"description": "Removed path"
},
{
"kind": "property_type_changed",
"path": "User.email",
"description": "Property type changed",
"old_value": "string",
"new_value": "object"
}
],
"dangerous_changes": [...],
"migration_steps": [
"Remove client code referencing removed paths: `/api/v1/users`",
"Update client code to stop calling removed operations: `GET /api/v1/users`",
"Add required parameters to requests: `GET /api/v2/users.limit`",
"Update type handling code for changed types",
"Update enum value references to remove deleted values"
]
}
Programmatic rendering: Feed the JSON output into your docs generator, Slack bot, or support portal to create branded migration guides automatically. The structured data makes it easy to render breaking changes prominently, hide non-breaking changes by default, or translate steps into other languages.
What the Migration Guide Covers
API Contract Guardian detects 12 categories of changes and ranks them by severity:
| Change Type | Severity | Migrate Action |
|---|---|---|
| Path removed | Breaking | Remove client code referencing the path |
| Operation removed | Breaking | Stop calling the removed operation |
| Property type changed | Breaking | Update type handling code |
| Parameter became required | Breaking | Add the required parameter to requests |
| Request body became required | Breaking | Include request body in all calls |
| Schema removed | Breaking | Replace removed schema references |
| Enum values removed | Dangerous | Remove deleted enum values from code |
| Property removed | Breaking | Remove property references from code |
| Content type removed | Breaking | Update content-type handling |
| OperationId changed/removed | Breaking | Update SDK codegen references |
| New path added | Non-breaking | Optional: adopt new endpoints |
| New optional property | Non-breaking | Optional: consume new fields |
CI/CD Integration: Auto-Generate Migration Guides on Spec Changes
Pattern 1: Generate Migration Guide on PR
When someone changes the OpenAPI spec, automatically post a migration guide as a PR comment:
# .github/workflows/api-migration-guide.yml
name: API Migration Guide
on:
pull_request:
paths: ['openapi.yaml', 'specs/**']
jobs:
migration-guide:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Need full history for diff
- name: Install API Contract Guardian
run: pip install api-contract-guardian
- name: Get base spec
run: |
git checkout origin/${{ github.base_ref }}
cp openapi.yaml /tmp/spec-old.yaml
git checkout -
- name: Generate migration guide
run: |
api-contract-guardian migrate /tmp/spec-old.yaml openapi.yaml \
--output MIGRATION.md
- name: Post migration guide as PR comment
run: |
BODY=$(cat MIGRATION.md)
gh pr comment ${{ github.event.pull_request.number }} \
--body "## 🔄 Auto-Generated Migration Guide
$BODY"
Pattern 2: Gate PRs on Breaking Changes + Generate Guide
Combine check (CI gating) with migrate (guide generation) — block the PR if there are breaking changes, and show the migration guide so the reviewer knows exactly what they'd be signing consumers up for:
# .github/workflows/api-contract-check.yml
name: API Contract Check
on:
pull_request:
paths: ['openapi.yaml']
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Install
run: pip install api-contract-guardian
- name: Get base spec
run: |
git checkout origin/${{ github.base_ref }}
cp openapi.yaml /tmp/spec-old.yaml
git checkout -
- name: Check for breaking changes (exit 1 = block PR)
run: |
api-contract-guardian check /tmp/spec-old.yaml openapi.yaml
- name: Generate migration guide (runs only if check fails)
if: failure()
run: |
api-contract-guardian migrate /tmp/spec-old.yaml openapi.yaml \
--output MIGRATION.md
- name: Post migration guide
if: failure()
run: |
gh pr comment ${{ github.event.pull_request.number }} \
--body-file MIGRATION.md
Workflow: The check command blocks the PR with exit code 1. The migrate command generates the guide so reviewers (and eventually consumers) know what changed. When the team decides the breaking changes are intentional, they can merge — and the migration guide is already written.
Pattern 3: Auto-Commit Migration Guide on Version Bump
When you tag a new API version, auto-generate and commit the migration guide:
# .github/workflows/api-release.yml
name: API Release
on:
push:
tags: ['api-v*']
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install
run: pip install api-contract-guardian
- name: Get previous version spec
run: |
PREV_TAG=$(git tag --sort=-version:refname | head -2 | tail -1)
git checkout "$PREV_TAG"
cp openapi.yaml /tmp/spec-old.yaml
git checkout -
- name: Generate migration guide
run: |
api-contract-guardian migrate /tmp/spec-old.yaml openapi.yaml \
--format markdown \
--output docs/migration-guide-${{ github.ref_name }}.md
- name: Commit migration guide
run: |
git add docs/migration-guide-*.md
git commit -m "Add migration guide for ${{ github.ref_name }}"
git push
Three Real Migration Scenarios
Scenario 1: SaaS API v1 to v2 Upgrade
Your SaaS platform is moving from v1 to v2. You've renamed several endpoints, changed user_id from integer to UUID, removed the deprecated /api/v1/search endpoint, and added required pagination parameters. That's 7+ individual changes that consumers need to handle.
Without auto-generation: The PM writes a migration doc over 3 days. It misses the user_id type change. Consumers' integration tests fail in production because their code casts the UUID to an integer.
With API Contract Guardian: migrate generates a guide listing all 7 changes ranked by severity. The user_id type change is flagged as breaking with before/after values. The migration steps say "Update type handling code for changed types." Consumers get the complete list before they start migrating.
Scenario 2: Microservice Contract Enforcement
Team A owns the User Service. Team B owns the Order Service, which calls Team A's API. Team A changes their spec — they remove the /users/{id}/permissions endpoint and replace it with a new /users/{id}/roles endpoint. They don't tell Team B directly.
With the CI pipeline: Team A's PR triggers check — it fails because an endpoint was removed. The migrate command generates the migration guide automatically and posts it as a PR comment. Team B's tech lead reviews the PR, sees the migration guide, and starts updating the Order Service before Team A even merges.
Scenario 3: SDK Regeneration After Spec Change
You maintain SDKs in Python, TypeScript, and Go. Every time the OpenAPI spec changes, you need to regenerate the SDKs. But which changes matter? Which ones require code changes vs. which are just additive?
With the JSON migration output: migrate --format json gives you a structured list of changes with severity. Your SDK generation pipeline reads the JSON, regenerates only the affected models/endpoints, and flags breaking changes for manual review. Non-breaking additions land automatically; breaking changes require a human to sign off.
Migration Guide vs. Check: When to Use Each
check |
migrate |
|
|---|---|---|
| Purpose | Block or allow | Explain and guide |
| Output | Pass/fail (exit code) | Markdown, JSON, or YAML guide |
| CI use | PR gating — break the build on breaking changes | PR comments, release docs, consumer guides |
| Audience | CI pipeline | API consumers, SDK maintainers, support team |
| When to use | Every PR that touches the spec | When breaking changes exist (or are about to ship) |
Use both together: Run check first to gate the PR. If it fails, run migrate to generate the guide. This gives you CI safety and consumer documentation in one workflow.
Customizing the Output
Pipe Markdown into Your Docs Site
# Generate and push to a docs branch
api-contract-guardian migrate spec-v1.yaml spec-v2.yaml \
--format markdown \
--output docs/migrations/v2.md
# Or pipe directly into a docs generator
api-contract-guardian migrate spec-v1.yaml spec-v2.yaml | \
pandoc -f markdown -t html > docs/migrations/v2.html
Process JSON in a Custom Pipeline
# Extract just the breaking changes and migration steps
api-contract-guardian migrate spec-v1.yaml spec-v2.yaml --format json | \
python3 -c "
import sys, json
guide = json.load(sys.stdin)
if guide['summary']['breaking'] > 0:
print(f'⚠️ {guide[\"summary\"][\"breaking\"]} breaking changes detected')
for step in guide['migration_steps']:
print(f' → {step}')
else:
print('✓ No breaking changes — safe to upgrade')
"
YAML for Kubernetes-Native Workflows
# Generate YAML migration guide for ConfigMap mounting
api-contract-guardian migrate spec-v1.yaml spec-v2.yaml \
--format yaml \
--output k8s/migration-guide.yaml
Install API Contract Guardian
# pip
pip install api-contract-guardian
# Homebrew (macOS / Linux)
brew tap Coding-Dev-Tools/tap
brew install api-contract-guardian
# Scoop (Windows)
scoop bucket add Coding-Dev-Tools https://github.com/Coding-Dev-Tools/scoop-bucket
scoop install api-contract-guardian
Star API Contract Guardian on GitHub
Related Reading
- Catch Breaking API Changes in CI — API Contract Guardian CI gating tutorial
- OpenAPI Diffing Tools Compared — API Contract Guardian vs Spectral vs OAS Diff
- Catch Data Schema Drift in CI — DataMorph schema validation
- Block Deployments on Config Drift — ConfigDrift CI/CD gating