API Contract Guardian: Git-Branch-Aware OpenAPI Diffing for Every PR
Stop manually reviewing OpenAPI spec changes. Diff specs between git branches, detect breaking changes in every PR, and generate migration guides automatically — with the change-severity taxonomy that code review misses.
Here's how API changes usually break production: someone on a feature branch renames a field, removes an endpoint, or changes a type. The change looks fine in code review — the code compiles, the tests pass, and the PR gets merged. Three days later, a downstream service starts returning 500s because it's still sending the old field name.
Code review catches logic bugs. It does not reliably catch contract violations — there are too many paths, too many consumers, and the reviewer doesn't know every client that depends on every field.
API Contract Guardian solves this by diffing your OpenAPI specs between git branches and classifying every change by severity: breaking, dangerous, non-breaking, or informational.
The Workflow: PR-Level API Change Detection
The pattern is simple but powerful:
- Before merge: Diff the OpenAPI spec on the feature branch against main
- If breaking changes found: Block the merge (or require explicit approval)
- If migration needed: Auto-generate the migration guide
- After merge: The migration guide is already written and ready to share
Let's set this up step by step.
Step 1: Local Branch Diffing
Before you even push, check your branch against main locally:
# Check out your feature branch
git checkout feature/add-user-phone
# Diff your spec against main — API Contract Guardian loads both from git
api-contract-guardian check main:openapi.yaml feature/add-user-phone:openapi.yaml
Or compare two files directly if you have them checked out:
# Compare the spec on your branch with the one on main
git show main:openapi.yaml > /tmp/spec-old.yaml
api-contract-guardian check /tmp/spec-old.yaml openapi.yaml
Output shows every detected change, classified by severity:
┌─────────── Change Summary ───────────┐
│ Severity Count │
│ Breaking 2 │
│ Dangerous 1 │
│ Non-breaking 3 │
│ Info 1 │
└───────────────────────────────────────┘
Breaking Changes:
- property_became_required at components.schemas.User.phone: Property 'phone' in schema 'User' became required
- property_removed at components.schemas.User.properties.nickname: Property 'nickname' removed from schema 'User'
Dangerous Changes:
- operation_deprecated at paths./users/{id}.get: GET /users/{id} is now deprecated
Non-Breaking Changes:
+ path_added at paths./users/search: Path '/users/search' was added
+ property_added at components.schemas.User.properties.phone: Property 'phone' added to schema 'User'
+ schema_added at components.schemas.UserSearch: Schema 'UserSearch' was added
Step 2: CI Gate on Every Pull Request
The real power is running the check automatically in CI. Here's how to set it up:
GitHub Actions
name: API Contract Check
on:
pull_request:
paths:
- 'openapi.yaml'
- 'openapi.yml'
- 'specs/**'
jobs:
api-contract-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for branch diffing
- name: Install API Contract Guardian
run: pip install api-contract-guardian
- name: Extract base spec from target branch
run: |
git checkout ${{ github.base_ref }}
cp openapi.yaml /tmp/spec-base.yaml
git checkout ${{ github.head_ref }}
- name: Check for breaking changes
run: |
api-contract-guardian check /tmp/spec-base.yaml openapi.yaml \
--fail-on-breaking --format json --output contract-check.json
- name: Upload contract check results
if: always()
uses: actions/upload-artifact@v4
with:
name: api-contract-check
path: contract-check.json
- name: Generate migration guide if needed
if: failure()
run: |
api-contract-guardian migrate /tmp/spec-base.yaml openapi.yaml \
--output MIGRATION.md
echo "::warning::Breaking API changes detected. See MIGRATION.md for required consumer updates."
- name: Comment migration guide on PR
if: failure()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const migration = fs.readFileSync('MIGRATION.md', 'utf8');
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: '## ⚠️ Breaking API Changes Detected\n\n' + migration
});
GitLab CI
api-contract-check:
image: python:3.12
rules:
- if: $CI_MERGE_REQUEST_IID
changes:
- openapi.yaml
- specs/**/*
script:
- pip install api-contract-guardian
- |
git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
git show origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME:openapi.yaml > /tmp/spec-base.yaml
api-contract-guardian check /tmp/spec-base.yaml openapi.yaml \
--fail-on-breaking --format json --output contract-check.json
- |
if [ $? -ne 0 ]; then
api-contract-guardian migrate /tmp/spec-base.yaml openapi.yaml --output MIGRATION.md
echo "Breaking changes detected. See MIGRATION.md:"
cat MIGRATION.md
fi
artifacts:
when: always
paths:
- contract-check.json
- MIGRATION.md
Step 3: The Four-Level Severity Taxonomy
API Contract Guardian doesn't just say "something changed." It classifies every change into four severity levels that map directly to actions:
| Severity | Meaning | Action | Examples |
|---|---|---|---|
| Breaking | Existing clients will break | Block merge, require migration plan | Removed endpoint, removed property, changed type, required-property added |
| Dangerous | May break clients depending on usage | Flag for review, don't auto-block | Deprecated operation, format change, server removed |
| Non-breaking | Safe — clients continue working | Allow merge | Added endpoint, added property, added schema, optional param added |
| Info | Metadata changes, no contract impact | Noise — informational only | API version bumped, title changed, server added |
What Gets Detected
The diff engine checks six categories of changes:
- Paths & Operations — removed endpoints, removed methods, new required parameters
- Schemas & Components — removed properties, type changes, required-property additions, enum narrowing
- Security Schemes — removed or changed authentication methods
- Global Security — added or removed top-level security requirements
- Servers — server URL changes
- Info Metadata — title or version changes
Step 4: Fine-Grained CI Gating
Not every team wants to block on every breaking change. API Contract Guardian gives you control:
# Strict: block on any breaking change (default)
api-contract-guardian check spec-old.yaml spec-new.yaml --fail-on-breaking
# Strict: also block on dangerous changes
api-contract-guardian check spec-old.yaml spec-new.yaml \
--fail-on-breaking --fail-on-dangerous
# Budget: allow up to 2 breaking changes (e.g., during a v2 migration)
api-contract-guardian check spec-old.yaml spec-new.yaml \
--max-breaking 2
# Allow dangerous changes but cap at 3
api-contract-guardian check spec-old.yaml spec-new.yaml \
--fail-on-breaking --max-dangerous 3
--max-breaking N to allow a controlled number of breaking changes while still catching accidental ones beyond the budget.
Step 5: Auto-Generated Migration Guides
When the CI gate fails, the next question is always: "What do consumers need to change?" API Contract Guardian answers this automatically:
# Generate a migration guide from the diff
api-contract-guardian migrate spec-v1.yaml spec-v2.yaml --output MIGRATION.md
The generated migration guide includes:
- Summary — total breaking, dangerous, and non-breaking changes
- Breaking changes with action items — what was removed/changed and what consumers must update
- Deprecated endpoints — warning that certain operations will be removed
- New features — endpoints and properties added that consumers can adopt
You can also generate migration guides in JSON or YAML for integration with documentation systems:
# JSON migration guide for doc tooling
api-contract-guardian migrate spec-v1.yaml spec-v2.yaml --format json --output migration.json
# YAML for CI artifact storage
api-contract-guardian migrate spec-v1.yaml spec-v2.yaml --format yaml --output migration.yaml
Step 6: Monorepo Support — Diffs Within a Repo
If your API specs live alongside your code (as they should), you can diff them from any two branches, tags, or commits:
# Compare current branch spec against the last release tag
git show v2.3.0:services/api/openapi.yaml > /tmp/spec-release.yaml
api-contract-guardian check /tmp/spec-release.yaml services/api/openapi.yaml
# Compare against a specific commit
git show abc1234:openapi.yaml > /tmp/spec-old.yaml
api-contract-guardian check /tmp/spec-old.yaml openapi.yaml
# In a monorepo with multiple services
for service in services/*/; do
if [ -f "$service/openapi.yaml" ]; then
echo "Checking $service..."
git show main:"$service/openapi.yaml" > /tmp/spec-base.yaml
api-contract-guardian check /tmp/spec-base.yaml "$service/openapi.yaml" \
--format json --output "$service/contract-check.json"
fi
done
Step 7: Pre-Commit Hook for Local Enforcement
Catch breaking changes before they even reach CI — add a pre-commit hook:
# .git/hooks/pre-commit (or via husky/lint-staged)
#!/bin/bash
# Only run if OpenAPI spec has changed
if git diff --cached --name-only | grep -q "openapi.yaml"; then
echo "OpenAPI spec changed — running contract check..."
git show HEAD:openapi.yaml > /tmp/spec-head.yaml
if ! api-contract-guardian check /tmp/spec-head.yaml openapi.yaml --fail-on-breaking; then
echo ""
echo "❌ Breaking API changes detected. Run this for details:"
echo " api-contract-guardian migrate /tmp/spec-head.yaml openapi.yaml"
exit 1
fi
echo "✅ No breaking API changes detected."
fi
Putting It Together: The Complete PR Workflow
┌──────────────────────────────────────────────────┐
│ Developer workflow │
│ │
│ 1. Create feature branch │
│ 2. Modify openapi.yaml │
│ 3. Run: api-contract-guardian check │ ← Local check (30s)
│ 4. If breaking: generate migration guide │ ← Fix or document
│ 5. Push & open PR │
│ 6. CI runs: api-contract-guardian check │ ← Automated gate
│ 7. If gate fails: migration guide posted to PR │ ← Auto-comment
│ 8. Team reviews changes + migration guide │ ← Informed review
│ 9. Approve or request changes │
│ 10. Merge with confidence │
└──────────────────────────────────────────────────┘
Why This Beats Manual Spec Review
| Manual spec review | API Contract Guardian |
|---|---|
| Reviewer must memorize all consumer dependencies | Every change classified by severity automatically |
| Breaking changes can slip through on large diffs | No change is missed — every path, schema, and security scheme is checked |
| Migration guides written manually (often skipped) | Auto-generated with actionable steps |
| Different reviewers apply different standards | Consistent severity taxonomy on every PR |
| Monorepo: specs across services hard to compare | Branch-aware diffing from any ref |
Getting Started
Guard your API contracts on every PR
Stop relying on reviewers to catch breaking API changes manually. Automate the check, gate the merge, generate the migration guide.
pip install api-contract-guardian
api-contract-guardian check main:openapi.yaml HEAD:openapi.yaml
View on GitHub →
API Contract Guardian is part of the DevForge developer tool ecosystem — 11 CLI tools built by autonomous AI for autonomous developers.