Diffs two OpenAPI versions for breaking changes (removed endpoints, deleted schemas, narrowed enums, response shape changes) using `oasdiff breaking`, severity-classifies the findings (ERR / WARN / INFO), and gates CI with `--fail-on`. Use when reviewing an OpenAPI spec change in a PR or on a release tag.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
oasdiff is the canonical CLI for comparing two OpenAPI specifications
and reporting breaking changes (oasdiff-readme). It runs
over 250 checks against the diff and classifies each finding into one
of three severity tiers (oasdiff-breaking):
| Severity | Meaning |
|---|---|
ERR | Definite breaking change - should be avoided. |
WARN | Potential breaking change - developers should be aware. |
INFO | Non-breaking change. |
This is the schema-driven counterpart to
pact-contract-testing: use
oasdiff when you don't control the consumers (public APIs, third-
party integrations) and need to gate breaking-change shipment based on
the spec alone.
openapi.yaml, openapi.json,
or split-file equivalent).Unlike Pact, oasdiff doesn't add tests to the repo. Instead it consumes two OpenAPI specs and emits a diff. Authoring effort goes into keeping the spec authoritative - i.e. ensuring the OpenAPI file in the repo accurately describes the implementation. Tools that close the spec-vs-implementation gap (Schemathesis, Spectral lint) pair well with this skill but are out of scope.
# Go install (preferred for native CLI)
go install github.com/oasdiff/oasdiff@latest
# Docker (CI-friendly, no Go toolchain required)
docker pull tufin/oasdiff(Per oasdiff-readme.)
oasdiff breaking openapi-old.yaml openapi-new.yaml(Per oasdiff-breaking.)
By default oasdiff prints colorized text. For machine consumption, use
--format:
oasdiff breaking --format json openapi-old.yaml openapi-new.yaml > breaking.json
oasdiff breaking --format yaml openapi-old.yaml openapi-new.yaml
oasdiff breaking --format markdown openapi-old.yaml openapi-new.yaml
oasdiff breaking --format html openapi-old.yaml openapi-new.yaml > breaking.htmlAvailable formats: text (default), JSON, YAML, markdown, HTML, plus single-line (oasdiff-breaking).
The CLI accepts URLs directly:
oasdiff breaking \
https://raw.githubusercontent.com/.../openapi-test1.yaml \
https://raw.githubusercontent.com/.../openapi-test3.yaml(Adapted from oasdiff-readme.)
docker run --rm -t -v "$PWD:/specs" tufin/oasdiff breaking \
/specs/openapi-old.yaml /specs/openapi-new.yaml--fail-on for CI gatingThe exit code is the gate signal (oasdiff-breaking):
oasdiff breaking --fail-on ERR ... # exit 1 on ERR; default behavior most teams want
oasdiff breaking --fail-on WARN ... # exit 1 on ERR or WARN; stricter
oasdiff breaking --fail-on INFO ... # exit 1 on any reported changeDefault for production gates: --fail-on ERR. Use --fail-on WARN for
stricter projects (financial APIs, public SDKs) where even potentially-
breaking changes need explicit acceptance.
oasdiff ships 250+ checks. The headline breaking patterns (oasdiff-breaking):
responses block.components.schemas - even if no longer referenced.enum array.
Mark the enum x-extensible-enum: true to treat removals as
non-breaking (clients should ignore unknown values).string
to integer, removing a required array entry, etc.required - making a previously-
optional request field required.For the full list, run oasdiff checks to dump the active rule set.
Text output (default) example:
1 changes: 1 error, 0 warning, 0 info
error [response-success-status-removed] at openapi-new.yaml
in API GET /pets
the success response status '200' was removedJSON output (for downstream parsing):
[
{
"id": "response-success-status-removed",
"level": 3,
"operation": "GET",
"operationId": "listPets",
"path": "/pets",
"source": "openapi-new.yaml",
"section": "paths",
"text": "the success response status '200' was removed"
}
]level: 3 is ERR, 2 is WARN, 1 is INFO per the standard
oasdiff convention. Pipe to jq for triage:
jq -r '.[] | select(.level==3) | "ERR " + .operation + " " + .path + " :: " + .text' breaking.jsonGitHub Actions example using the Docker image and the --fail-on ERR
default:
# .github/workflows/openapi-diff.yml
name: openapi-diff
on:
pull_request:
jobs:
oasdiff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0 # need merge-base SHA for `git show <base>:openapi.yaml`
- name: Extract base spec
run: |
BASE_SHA=$(git merge-base origin/main HEAD)
git show $BASE_SHA:openapi.yaml > /tmp/openapi-base.yaml
- name: Run oasdiff
run: |
docker run --rm -v "$PWD:/specs" -v /tmp:/tmp tufin/oasdiff breaking \
--fail-on ERR \
--format text \
/tmp/openapi-base.yaml \
/specs/openapi.yaml | tee oasdiff-report.txt
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: openapi-breaking-report
path: oasdiff-report.txt
retention-days: 14fetch-depth: 0 is required so the merge-base of the PR resolves; the
shallow clone GitHub uses by default doesn't include the merge-base
commit.
For a PR comment workflow, generate --format markdown and post via
actions/github-script@v7 - the markdown table format includes a
clickable per-finding rationale.
breaking subcommand, severity tiers,
--fail-on semantics, breaking-change categories.pact-contract-testing - the
consumer-driven counterpart for cases where you control consumers.contract-compatibility-gate - the gate skill that aggregates oasdiff output with Pact and
Protobuf verdicts.