Catch Breaking OpenAPI Changes Before Release with oasdiff
Diff two OpenAPI documents with oasdiff in CI, fail the build on removed fields or narrowed types, and publish a human-readable changelog for review.
Task
Compare the current OpenAPI spec against the released one in CI and block merges that remove or narrow public API surface.
When to use this
A refactor renamed a response field. Tests passed, the deploy was green, and three client SDKs broke in production within the hour.
Prerequisites
- OpenAPI 3 documents generated in CI from the current build
- The released spec accessible by URL or artifact
- oasdiff available as a Docker image or binary in CI
Steps
1. Generate the current spec and download the released one.
curl -s https://spec.example.com/releases/openapi-v3.2.json -o released.json && npm run generate:openapiExpected output: released.json contains the public contract and the fresh build writes the candidate spec to compare against it.
2. Run a breaking-change check locally first.
oasdiff breaking released.json openapi.jsonExpected output: Removed response fields print with the exact location, for example response 200 field order.items was removed.
3. Get the exit code behavior right for CI.
oasdiff breaking released.json openapi.json --fail-on ERR --format text; echo "exit=$?"Expected output: Error-level findings exit non-zero while warnings pass, which is the right default gate for a merge pipeline.
4. Wire it into CI with the Docker image.
- name: Breaking change gate
run: |
curl -s "$RELEASED_SPEC_URL" -o released.json
docker run --rm -v "$PWD:/spec" redocly/oasdiff breaking \
/spec/released.json /spec/openapi.json --fail-on ERR
Expected output: The pull request fails with a list of exact breaking changes instead of shipping them silently.
5. Also emit the changelog artifact for reviewers.
oasdiff changelog released.json openapi.json > api-changelog.mdExpected output: A markdown changelog lands in the build artifacts, so reviewers approve API changes consciously rather than by accident.
Troubleshooting
Problem
Diff reports noise on reordered fields
Solution
JSON key order is not semantic; ensure both specs are generated by the same toolchain version so ordering is stable, or sort keys before diffing.
Problem
Intentional breaking change is blocked
Solution
Version the API path or media type instead of weakening the gate; a deliberate break belongs in a new major version with its own spec baseline.
Problem
Spec generation differs between environments
Solution
Generate the spec inside CI in a controlled job rather than committing a hand-edited file, so the diff compares code truth against code truth.
Limitations
- The tool compares documents, not behavior; a spec-identical change can still break clients through semantics such as new validation.
- Undocumented endpoints are invisible to the diff, so the gate only protects what the spec actually describes.
The contract nobody was checking
Your OpenAPI document is a promise to every client you cannot see. Refactors rename response fields, tests stay green because the server is consistent with itself, and the break only appears in someone else build. Comparing the released spec against the freshly generated one makes that promise enforceable: removals and narrowed types fail the pipeline, and the changelog output turns invisible API edits into a reviewable artifact.
oasdiff breaking released.json openapi.json --fail-on ERR
# Also produce a reviewer-facing changelog
oasdiff changelog released.json openapi.json > api-changelog.md
Fail on errors, warn on the rest
Not every diff is equally severe: removing a response field is a hard error, adding an optional request parameter is usually a warning. Gate the pipeline on error-severity findings only, and route warnings into the changelog. That keeps the gate strict where clients break and quiet enough that people keep it enabled.
Baseline per major version
When a breaking change is genuinely intended, do not weaken the gate; start a new major version with a new baseline spec. The comparison then stays meaningful inside the version, and clients get a clean migration story instead of a silent field rename.