Skip to main content

Detect breaking changes between API schemas (OpenAPI / JSON Schema)

Project description

api-schema-diff

Detect breaking changes between API schemas (OpenAPI / JSON Schema) in a deterministic, CI-friendly way.

api-schema-diff is a CLI tool designed to answer one question reliably:

"Will this schema change break existing clients?"


Why this exists

API changes often break clients silently.

Common causes:

  • Removing endpoints
  • Removing fields
  • Changing parameter requirements
  • Changing request/response schemas

api-schema-diff catches these issues before they reach production.


Features

Supported inputs

  • OpenAPI 3.x (JSON / YAML)
  • JSON Schema (generic structure diff)

Breaking changes detected

  • Removed paths or operations
  • Removed query / path / header parameters
  • Parameters becoming required
  • Parameter schema type changes
  • Request body removed
  • Request body becoming required
  • Request body schema breaking changes
  • Removed response status codes
  • Response schema breaking changes
  • Removed properties in schemas
  • Property type changes
  • Optional → required fields

Non-breaking changes detected

  • Added paths or operations
  • Added optional parameters
  • Added optional request bodies
  • Added response status codes
  • Added optional properties

Designed for CI

  • Deterministic output
  • Stable exit codes
  • JSON output for automation
  • Works offline (no cloud / no LLM dependency)

Installation

pip install api-schema-diff

Or install from source:

git clone https://github.com/teolzr/schema-diff.git
cd schema-diff
pip install -e .

Requirements:

  • Python 3.10 or higher
  • typer>=0.12
  • rich>=13.7

Usage

Basic usage

api-schema-diff old.json new.json

Exit codes:

  • 0 → No breaking changes found
  • 1 → Breaking changes detected

Output formats

Text output (default):

api-schema-diff old.json new.json

Output:

BREAKING CHANGES FOUND

┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Type         ┃ Path        ┃ Old Type┃ New Type┃ Message               ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━┩
│ removed_field│ User.email  │         │         │                       │
│ type_change  │ Order.amount│ <number>│ <string>│                       │
└──────────────┴─────────────┴─────────┴─────────┴───────────────────────┘

JSON output:

api-schema-diff old.json new.json --format json

Output:

{
  "breaking": [
    {
      "type": "removed_field",
      "severity": "breaking",
      "path": "User.email",
      "old_type": null,
      "new_type": null,
      "message": null
    },
    {
      "type": "type_change",
      "severity": "breaking",
      "path": "Order.amount",
      "old_type": "<number>",
      "new_type": "<string>",
      "message": null
    }
  ],
  "non_breaking": []
}

CLI Options

api-schema-diff [OPTIONS] OLD_FILE NEW_FILE

Arguments:

  • OLD_FILE - Path to the old schema file (JSON or YAML)
  • NEW_FILE - Path to the new schema file (JSON or YAML)

Options:

  • --format [text|json] - Output format (default: text)
  • --fail-on-breaking / --no-fail-on-breaking - Exit with code 1 when breaking changes are found (default: true)
  • --help - Show help message

Report-only mode

Use --no-fail-on-breaking to always exit with code 0 (useful for reporting without failing CI):

api-schema-diff old.json new.json --no-fail-on-breaking

Examples

Example 1: Generic JSON diff

old.json:

{
  "User": {
    "email": "a@b.com",
    "age": 30
  },
  "Order": {
    "amount": 12.5
  }
}

new.json:

{
  "User": {
    "age": "30"
  },
  "Order": {
    "amount": "12.5"
  },
  "NewThing": {}
}
api-schema-diff old.json new.json

Detected changes:

  • Breaking: User.email field removed
  • Breaking: User.age type changed from number to string
  • Breaking: Order.amount type changed from number to string
  • Non-breaking: NewThing object added

Example 2: OpenAPI diff

old-api.yaml:

openapi: 3.0.0
paths:
  /users:
    get:
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  email:
                    type: string

new-api.yaml:

openapi: 3.0.0
paths:
  /users:
    get:
      parameters:
        - name: limit
          in: query
          required: true  # Now required!
          schema:
            type: integer
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  # email removed!
api-schema-diff old-api.yaml new-api.yaml

Detected changes:

  • Breaking: Query parameter limit became required
  • Breaking: Response property email removed

CI/CD Integration

GitHub Actions

name: Schema Diff Check

on:
  pull_request:
    paths:
      - 'api/schema.yaml'

jobs:
  schema-diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10'

      - name: Install api-schema-diff
        run: pip install api-schema-diff

      - name: Get old schema from main branch
        run: git show origin/main:api/schema.yaml > old-schema.yaml

      - name: Check for breaking changes
        run: api-schema-diff old-schema.yaml api/schema.yaml

GitLab CI

api-schema-diff:
  image: python:3.10
  before_script:
    - pip install api-schema-diff
  script:
    - git show origin/main:api/schema.yaml > old-schema.yaml
    - api-schema-diff old-schema.yaml api/schema.yaml
  only:
    changes:
      - api/schema.yaml

Pre-commit hook

Create .pre-commit-config.yaml:

repos:
  - repo: local
    hooks:
      - id: api-schema-diff
        name: Check API schema for breaking changes
        entry: bash -c 'git show HEAD:api/schema.yaml > /tmp/old-schema.yaml && api-schema-diff /tmp/old-schema.yaml api/schema.yaml'
        language: system
        files: 'api/schema.yaml'
        pass_filenames: false

Development

Setup

# Clone the repository
git clone https://github.com/teolzr/schema-diff.git
cd schema-diff

# Install in development mode
pip install -e .

# Install development dependencies
pip install pytest pytest-cov black ruff

Running tests

# Run all tests
pytest

# Run with coverage
pytest --cov=schema_diff --cov-report=html

# Run specific test file
pytest tests/test_openapi_diff.py

Note: The Python package is still named schema_diff internally, but the PyPI package and CLI command are api-schema-diff.

Code formatting

# Format code
black .

# Lint code
ruff check .

Project structure

schema-diff/
├── schema_diff/
│   ├── __init__.py
│   ├── cli.py          # CLI entry point
│   ├── diff.py         # Generic JSON diff logic
│   ├── loader.py       # Schema file loading
│   ├── models.py       # Data models
│   ├── rules.py        # Breaking change rules
│   └── openapi/
│       ├── __init__.py
│       ├── diff.py             # OpenAPI-specific diff
│       ├── json_schema_diff.py # JSON Schema diffing
│       ├── normalizer.py       # Schema normalization
│       └── resolver.py         # $ref resolution
├── tests/
├── pyproject.toml
└── README.md

How it works

  1. Schema loading: Automatically detects schema type (OpenAPI vs generic JSON/YAML)
  2. Normalization: Resolves $ref references and normalizes structure
  3. Diffing: Compares schemas using rule-based detection
  4. Classification: Categorizes changes as breaking or non-breaking
  5. Reporting: Outputs results in human-readable or JSON format

Change detection logic

Breaking changes:

  • Removing existing fields/paths → clients expect them
  • Changing types → clients may send wrong data type
  • Making optional fields required → clients may not send them
  • Removing response fields → clients may depend on them

Non-breaking changes:

  • Adding new fields/paths → clients can ignore them
  • Making required fields optional → clients can still send them
  • Adding new optional fields → backwards compatible

Roadmap

  • Support for OpenAPI 2.0 (Swagger)
  • GraphQL schema diffing
  • Custom rule configuration
  • HTML report generation
  • API compatibility scoring
  • Severity levels (error, warning, info)

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add tests for new functionality
  5. Run tests and linting
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

License

MIT License - see LICENSE file for details


Credits

Built with:

  • Typer - CLI framework
  • Rich - Terminal formatting

Related Projects


Support

Note: Repository name is schema-diff, but PyPI package name is api-schema-diff

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

api_schema_diff-0.1.1.tar.gz (16.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

api_schema_diff-0.1.1-py3-none-any.whl (16.7 kB view details)

Uploaded Python 3

File details

Details for the file api_schema_diff-0.1.1.tar.gz.

File metadata

  • Download URL: api_schema_diff-0.1.1.tar.gz
  • Upload date:
  • Size: 16.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for api_schema_diff-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c640a58f6e8fb6ed39cc2e991d8ab855c8831db3a56898d67d0c5ed9cbee9a15
MD5 445eccae98dabc7a383e487872ba0d7d
BLAKE2b-256 2a5301c04032c3314ebb5835c041dc6eab0007fb142bade863c1344f42895554

See more details on using hashes here.

Provenance

The following attestation bundles were made for api_schema_diff-0.1.1.tar.gz:

Publisher: release.yml on teolzr/schema-diff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file api_schema_diff-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for api_schema_diff-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0ea33ef548df64db6b47db24dcc8922f768525fd04b9f8312c40cbec1e8ddebb
MD5 4d63905e06aa97994f27b236f3bc899e
BLAKE2b-256 2f49a38c0b1d8fadfc9bf2a0a529efaeacf21d183259bd701ce6f2053e739852

See more details on using hashes here.

Provenance

The following attestation bundles were made for api_schema_diff-0.1.1-py3-none-any.whl:

Publisher: release.yml on teolzr/schema-diff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page