Workflow Tutorial

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:

  1. Before merge: Diff the OpenAPI spec on the feature branch against main
  2. If breaking changes found: Block the merge (or require explicit approval)
  3. If migration needed: Auto-generate the migration guide
  4. 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
Run this before you push: Catching a breaking change locally saves the entire round-trip of PR creation, review, rejection, fix, and re-review. Thirty seconds of local checking saves thirty minutes of review cycles.

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:

SeverityMeaningActionExamples
BreakingExisting clients will breakBlock merge, require migration planRemoved endpoint, removed property, changed type, required-property added
DangerousMay break clients depending on usageFlag for review, don't auto-blockDeprecated operation, format change, server removed
Non-breakingSafe — clients continue workingAllow mergeAdded endpoint, added property, added schema, optional param added
InfoMetadata changes, no contract impactNoise — informational onlyAPI version bumped, title changed, server added

What Gets Detected

The diff engine checks six categories of 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
Migration-friendly gating: During major version bumps, use --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:

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 reviewAPI Contract Guardian
Reviewer must memorize all consumer dependenciesEvery change classified by severity automatically
Breaking changes can slip through on large diffsNo 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 standardsConsistent severity taxonomy on every PR
Monorepo: specs across services hard to compareBranch-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.