Your unit tests pass, you rename a response field, and the frontend breaks in production. ContractWatch snapshots your app's OpenAPI contract, then fails your commit or CI run if a change would break existing clients.
What it catches
- Breaking changes: removed endpoints, removed or retyped response fields, newly required request fields, removed status codes, narrowed enums.
- Schema-name collisions: two Pydantic models with the same name in different modules, which makes OpenAPI component names ambiguous.
- Reliability pitfalls: blocking calls inside
asyncendpoints, HTTP calls without a timeout, duplicate routes, conflicting operation IDs. - Fixes, not just errors: every finding says what broke, who it hurts, and how to fix it.
Install
pip install contractwatch # or: uv add --dev contractwatch
Python 3.11+, FastAPI, Pydantic v2.
Use it
contractwatch init # save the current contract as the baseline (commit .contractwatch/baseline.json)
contractwatch check # compare against the baseline
Exit codes: 0 safe, 1 blocking change, 2 analysis error.
Changed the API on purpose? Accept it:
contractwatch baseline update
Collisions that aren't breaking show up as warnings:
Commands
| Command | What it does |
|---|---|
init |
Create the baseline and optionally install the pre-commit hook |
check |
Compare against the baseline; saves a report on failure |
inspect |
Show the current routes and schemas |
baseline update |
Accept the current contract as the new baseline |
report --latest |
Re-open the last report (--format json, --output <path>, --list, --open) |
Run contractwatch <command> --help for all options.
Pre-commit and CI
contractwatch init offers to install the hook for you (skip with --no-install-hook). Or add it by hand:
repos:
- repo: local
hooks:
- id: contractwatch
name: ContractWatch
entry: contractwatch check
language: system
pass_filenames: false
In CI, just run contractwatch check; the exit code does the rest.
Configuration
Optional. Put it in contractwatch.toml or [tool.contractwatch] in pyproject.toml:
[tool.contractwatch]
app = "app.main:app" # skip auto-detection
severity_threshold = "error" # error | warning | info
baseline = ".contractwatch/baseline.json"
ignore = ["CW110"] # silence specific codes
[tool.contractwatch.retention]
keep = 20 # keep the last 20 reports...
days = 7 # ...or the last 7 days, whichever is stricter
Env vars: CONTRACTWATCH_HOME (reports/cache location), NO_COLOR.
Diagnostic codes
| Code | Meaning | Severity |
|---|---|---|
| CW101 | Removed endpoint | error |
| CW102 | Removed response field | error |
| CW103 | Incompatible type change | error |
| CW104 | Newly required request field | error |
| CW105 | Requiredness / nullability change | error |
| CW106 | Removed response status | error |
| CW107 | Breaking enum change | error |
| CW108 | Incompatible schema shape | error |
| CW110 | Compatible addition | info |
| CW201 | Confirmed schema collision | error |
| CW202 | Ambiguous repeated model name | warning |
| CW203 | Repeated model name, not exposed | info |
| CW204 | Component reference change | warning |
| RL101 | Blocking call in async endpoint | warning |
| RL102 | HTTP client call without a timeout | warning |
| RL103 | Duplicate route/method registration | warning |
| RL104 | Conflicting operationId values | warning |
Contributing
uv sync --group dev
uv run ruff check . && uv run mypy && uv run pytest -q
License
MIT
Metadata
Release files for contractwatch 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| contractwatch-1.0.0.tar.gz | 193.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| contractwatch-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 248.6 kB
Release files / contractwatch-1.0.0.tar.gz
| Download URL | contractwatch-1.0.0.tar.gz |
|---|---|
| Size | 193.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fb29c656fe0690b7978f420ec7648af96bc1b98ed0ffd2d5b5a6b4654557d893
|
|
BLAKE2b-256 checksum How to use checksums |
26bd0a3fdea8573f5d305558cf2114ef910c5691a7d9945ff71ff82dd0f33aee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.14
|
Release files / contractwatch-1.0.0-py3-none-any.whl
| Download URL | contractwatch-1.0.0-py3-none-any.whl |
|---|---|
| Size | 55.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
22ccd84d032b599b07ef5bb2a26ea4e3c5bc2fa77542d680920cd2ba8362c80a
|
|
BLAKE2b-256 checksum How to use checksums |
db3028259290cc063347e084b20c12d07f865fe0bf5ac33eb68610015f2a7e2e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.14
|
Security for contractwatch 1.0.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.