Skip to main content

fastapi-contract

Catch FastAPI breaking changes that OpenAPI diff can't see.

fastapi-contract compares your FastAPI application's declared runtime contracts with a saved baseline. It complements OpenAPI diff: a response key can disappear, input can stop reaching a field, or a hidden parameter can become required while the documented schema stays the same. SAFE means no incompatibility was detected in the supported contract surface; it is not a guarantee of full API compatibility.

Installation and supported versions

Requires Python 3.11+, FastAPI 0.141.1, and Pydantic 2.13.5. Install into the application's dependency environment:

pip install fastapi-contract

For a source checkout, use pip install .. FastAPI and Pydantic are pinned to the exact supported versions. Starlette follows FastAPI's dependency; use the same locked environment for baseline generation and checking. Baselines record exact Python, FastAPI, Starlette and Pydantic versions, including the Python patch version. Mismatches cause ERROR. CI verifies Python 3.11 and 3.12.

Quickstart

Create app/__init__.py and app/main.py in your project:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class User(BaseModel):
    id: int
    email: str


@app.get("/users", response_model=User)
def users():
    return {"id": 1, "email": "alice@example.com"}

Save the accepted application contract, then check proposed changes against it:

fastapi-contract snapshot app.main:app -o fastapi-contract-baseline.json
fastapi-contract check app.main:app --against fastapi-contract-baseline.json

New snapshots use schema v2 and include response, body binding, and parameter facts. An unchanged app prints:

SAFE: No incompatibility detected in the supported contract surface.

For example, add response_model_exclude={"email"} to the route decorator. The OpenAPI response schema still describes email, but runtime output loses it:

BREAKING
response-wire-surface GET /users: Response wire key "email" is no longer produced; key remains documented by OpenAPI.

The command exits 1. No server, endpoint, startup, or lifespan needs to run. App loading does execute module-level code; load trusted applications only. An invalid baseline is rejected before the target application is imported.

Supported checks

Owner Supported contract change
response-wire-surface Loss of an effective projected top-level response wire key, including response_model_by_alias blind spots. Ordinary documented key losses are delegated to OpenAPI diff.
request-body-binding Loss of a supported top-level JSON BaseModel input key's binding to a matched documented input slot. A field falling back to its default can be BREAKING even when HTTP stays 200.
hidden-parameter-binding Loss of a hidden Query, Header or Cookie wire binding.
hidden-parameter-requirement An existing, matched hidden parameter changes from optional to required. New required parameter additions are outside this release.

Parameter identity uses location and the resolved wire name. Header names are canonicalized to ASCII lowercase; Query and Cookie names are case-sensitive. Direct declarations and statically resolved dependencies are supported. Moving a parameter between them or renaming Python fields/arguments with stable wire bindings does not itself cause a finding.

Enum/nested sibling value types and field-local body validators do not automatically block unrelated supported keys. Relevant unresolved selectors, alias forms, model validators, parameter containers or dependency overrides can produce REVIEW for the affected candidate. Findings retain independent uncertainty alongside confirmed losses; BREAKING takes precedence over REVIEW without hiding those findings.

Results and CI

Result Stream Exit code
SAFE stdout 0
BREAKING stdout 1
REVIEW stdout 1
ERROR stderr 2

Successful snapshot exits 0. Invalid arguments, invalid snapshots, environment mismatch, app loading, file and analysis failures exit 2. Informational migration notices go to stderr and do not affect results or exit codes. Output has no color or TTY-dependent formatting; consumer wire names use JSON string escaping.

Commit the baseline alongside the application. With locked uv dependencies:

- run: uv sync --locked
- run: >-
    uv run --locked fastapi-contract check app.main:app
    --against fastapi-contract-baseline.json

Keep the exact Python patch version and lockfile consistent with baseline creation. Do not refresh baselines during ordinary CI checks. For an intentional contract change, review consumer impact and commit the accepted new baseline for review.

Upgrading a schema-v1 baseline

Existing schema-v1 baselines continue working and run legacy checks only. The result retains legacy FAPI001 meaning and output. A valid v1 check prints this notice exactly once to stderr, before loading the app:

Baseline uses snapshot schema v1.
Running legacy checks only.
Regenerate the baseline to enable current contract checks.

V1 does not contain enough information to recover current response facts for models it marked UNSUPPORTED, body binding, hidden parameter binding, or hidden requiredness. Legacy REVIEW limitations remain; a v1 SAFE result means only the legacy checks passed.

To enable current checks, check out the application revision whose contract you accept as the baseline, use its matching environment, and regenerate:

fastapi-contract snapshot app.main:app \
  -o fastapi-contract-baseline.json

Review and commit the result. Regeneration creates a new accepted baseline; it does not reconstruct historical facts. Generating from an already changed app accepts that app as the baseline. There is no automatic migration or migrate command. Explicit --schema-version 1 remains available, and check always selects its lane from the saved baseline version. Explicit --schema-version 2 also works.

Known limitations

This release does not add conditional response omission (exclude_none, exclude_unset, exclude_defaults), nested projection, full AliasChoices/AliasPath support, request coercion/strictness/validation-constraint checks, missing Content-Type behavior, route/slash resolution, hidden operation availability, Path parameter rules, or new required parameter additions. Arbitrary serializer, dependency, and user-code behavior is not analyzed. Relevant unsupported structures may require review; unrelated unsupported details do not imply every key is unknown.

Extraction checks declarations, not execution of arbitrary endpoint code. Routes registered only during startup/lifespan are not captured. There is no Git baseline loader, environment reconstruction, or automatic baseline refresh.

Development

uv sync --locked --group dev
uv run --locked pytest
uv run --locked ruff check .
uv run --locked ruff format --check .
uv run --locked mypy src
uv build --build-constraints tools/release/build-constraints.txt --require-hashes

See the release checklist, release notes, architecture, and testing strategy.

License

Apache License 2.0. See LICENSE.

Metadata

Release files for fastapi-contract 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fastapi-contract 0.2.0
File Size Uploaded
fastapi_contract-0.2.0.tar.gz 146.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fastapi-contract 0.2.0
File Interpreter ABI Platform
fastapi_contract-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 204.7 kB

Release files / fastapi_contract-0.2.0.tar.gz

Download URL fastapi_contract-0.2.0.tar.gz
Size 146.2 kB
Tags Source
SHA-256 checksum
How to use checksums
3fa2404e72b64f2e498a96cdad0ec0a493bdf31e360c1d37b4f8fe2f11154831
BLAKE2b-256 checksum
How to use checksums
0dcdf432304c1b5d0dc10fc31fc1f6b1efee830c14085c5808cb224fce353c5d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release files / fastapi_contract-0.2.0-py3-none-any.whl

Download URL fastapi_contract-0.2.0-py3-none-any.whl
Size 58.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f0b9797c12901a71d4d3262b63ee51a320bc125080a39b76b958f43601d8d0c5
BLAKE2b-256 checksum
How to use checksums
9a06f2d6777c04d1833587ae7c6a1e1eb02546f84a3dbbf1a85ba4b7b6a270b3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page