Migrate from oasdiff
This guide moves a repository that checks the changes of its OpenAPI description with oasdiff to
apigenie diff: the commands, the levels and the exit status, the checks as rules, deprecation
and sunset dates, the report in CI, and what has no counterpart. It starts from oasdiff 1.35 and
two versions of an OpenAPI file, v1.yaml and v2.yaml.
You need the CLI installed; the Quickstart shows how. Keep oasdiff installed until the last step, so you can compare the two reports on the same pair of files.
What changes and what stays
| oasdiff | API Genie | |
|---|---|---|
| Breaking changes | oasdiff breaking v1.yaml v2.yaml |
apigenie diff v1.yaml v2.yaml |
| Changelog | oasdiff changelog v1.yaml v2.yaml |
the same command: the changelog is the default report |
| Every change | oasdiff diff, oasdiff summary |
--report detailed, --report status |
| Levels | error, warning, info |
breaking, non-breaking, non-functional (severities) |
| Exit status | 0, unless --fail-on ERR or --fail-on WARN |
1 on a breaking change, 0 otherwise, 2 on a file it cannot read |
| Checks | 780 checks by id, listed by oasdiff checks changelog |
a rule per node type and property, such as operation.parameters.add_item (rules) |
| Configuration | .oasdiff.yaml, --severity-levels, --err-ignore, --warn-ignore |
apigenie.yaml, section commands.diff (Configuration) |
| Deprecation | x-sunset, --deprecation-days-stable, --deprecation-days-beta |
x-sunset, the sunset_passed condition, --today |
| Report formats | --format text, markdown, html, json, junit, githubactions, yaml, … |
--formatter stylish, markdown, html, json, unix, visualstudio |
| Standards | OpenAPI 3.x | OpenAPI 3.x, Swagger 2.0, AsyncAPI 3.x, Protobuf and GraphQL, each with rules of its own |
| Version | api-major-version-not-bumped reports a missing bump |
--update-version writes the version the changes call for |
| History | two files at a time | apigenie changelog openapi.yaml reads the versions from git (changelog) |
Run both on the same pair
Between v1.yaml and v2.yaml, GET /orders gets a required status parameter, the Order
schema of the responses loses its legacyCode property, and GET /legacy/export, deprecated with
x-sunset: "2026-09-30", is removed after that date.
oasdiff breaking v1.yaml v2.yaml
apigenie diff v1.yaml v2.yaml
1 changes: 1 error, 0 warning, 0 info
error [new-required-request-parameter] at v2.yaml
in API GET /orders
added the new required `query` request parameter `status`
v2.yaml
45 7 non-breaking Removed operation GET /legacy/export
10 7 breaking Modified operation GET /orders
10 7 breaking Added parameter status in query
10 7 breaking Modified response code 200
33 7 breaking Modified operation GET /orders/{orderId}
33 7 breaking Modified response code 200
Both rate the required parameter breaking, and both let the deprecated operation go: oasdiff
leaves the removal out of its breaking report, API Genie lists it as non-breaking. The removed
property is where they differ. oasdiff rates response-optional-property-removed as info; the
recommended rules rate a property removed from a
response as breaking, since a client may read it, unless the property was deprecated and its
x-sunset date has passed. --report detailed names the rule behind each line,
schema.properties.remove_item in the response layer here, so a disagreement is settled in the
configuration, next. The exit status differs too: apigenie diff exits with 1 on any breaking
change, where oasdiff needs --fail-on.
Replace the configuration
The severity of every change is a rule, so the flags and files that tune oasdiff become settings
of commands.diff in apigenie.yaml at the root of the repository. Here a changed title is
non-functional rather than breaking, and a removed response property is non-breaking, as oasdiff
has it:
version: '1.0'
commands:
diff:
extends: [recommended]
rules:
info:
title: non_functional
response:
schema:
properties:
remove_item: non_breaking
-
--fail-on ERRhas no flag: the command exits with 1 on a breaking change. oasdiff’s warnings are changes that may break a client, such asrequest-parameter-removed; the recommended rules rate most of them breaking already, so--fail-on WARNis the default exit status too. A change rated too low or too high gets another severity inrules, or inrequestandresponsefor the schemas of parameters and bodies and those of responses (direction). -
--severity-levels,--err-ignoreand--warn-ignorematch the lines of a report; a rule names the property instead.noneleaves a change out of the status and the changelog,non_functionalkeeps it in the changelog without failing the check. -
--match-path,--unmatch-pathand--filter-extensionbecomeoverrides, a list of rules applied to the locations that match a JSON pointer pattern, where*matches a part of a segment and**any number of segments. These leave the internal paths out:overrides: - where: location: /paths/~1internal~1*/** rules: pathItem: operations: none -
--exclude-elements description,summary,examples,extensionsis close to the default: descriptions, summaries, examples and extensions arenon_functional, in the changelog and never failing.noneon the property,operation: {description: none}, leaves them out. -
--auto-upgradehas no counterpart: a 3.0 and a 3.1 document are compared as they are, and the changed version is one more change, rated breaking by theopenapirule of theopenapinode;noneon it leaves it out. -
--flatten-allof,--flatten-params,--include-path-params,--composed,--prefix-base,--strip-prefix-base,--stability-level,--attributes,--langand--templatehave no counterpart.
Deprecate, then remove
Both tools read x-sunset, so the deprecations already in the document carry over. What is
checked differs:
- oasdiff reports deprecating an operation without a sunset date
(
api-deprecated-sunset-missing), a sunset date closer than--deprecation-days-stableor--deprecation-days-beta(api-sunset-date-too-small), and a removal before the date (api-removed-before-sunset). - API Genie rates deprecating as non-breaking, with or without a date, and the removal by the
sunset_passedcondition: a deprecated operation, path item, parameter, component schema, response property or request enum value may go once itsx-sunsetdate is on or before the date of the comparison, and is breaking before it. How far ahead the date is set is for the reviewer of the pull request, not a rule.
--today 2027-02-01 runs the comparison as of another date, to see a removal pass before then,
and apigenie evolve plan writes the deprecations and the removal
in two waves. Accepting a breaking change
lists the other ways forward.
The checks, as rules
The oasdiff checks most pull requests meet, and the rule that rates the same change; each rule
is a line of the OpenAPI tables, where the configuration
changes it. Checks whose id differs only by the kind of element, such as request-property-removed
and response-property-removed, follow the same rows.
| oasdiff | Level | API Genie | Note |
|---|---|---|---|
api-removed-without-deprecation, api-path-removed-without-deprecation |
error | pathItem.operations.remove_item, paths.remove_item |
breaking, unless sunset_passed |
api-removed-before-sunset, api-path-removed-before-sunset |
error | the same | |
api-path-removed-with-deprecation |
info | the same | non-breaking once the date has passed |
api-deprecated-sunset-missing, api-sunset-date-too-small |
error | not checked | operation.deprecated is non-breaking |
endpoint-added |
info | pathItem.operations.add_item |
non-breaking |
new-required-request-parameter |
error | operation.parameters.add_item |
breaking when required |
new-optional-request-parameter |
info | the same | non-breaking otherwise |
request-parameter-removed |
warning | operation.parameters.remove_item |
breaking, unless sunset_passed |
request-parameter-became-required |
error | parameter.required |
|
request-parameter-type-changed, response-property-type-changed |
error | schema.type |
breaking in both directions |
request-property-became-required, request-property-became-required-with-default |
error | schema.required.add_item in request |
the default does not matter |
response-required-property-removed |
error | schema.properties.remove_item in response |
breaking, unless sunset_passed |
response-optional-property-removed |
info | the same | breaking too; the configuration above lowers it |
response-property-became-optional |
error | schema.required.remove_item in response |
|
response-success-status-removed |
error | responses.remove_item |
every removed response code is breaking |
api-security-removed |
error | operation.security.remove_item |
|
api-operation-id-removed |
info | operation.operationId |
breaking: generated clients name their functions after it |
api-major-version-not-bumped |
info | --update-version |
writes the version instead of checking it |
The report in CI
On GitHub Actions, the oasdiff/oasdiff-action/breaking step with its base and revision
becomes the GitHub Action with command: diff. It compares each file
with its version at the base commit of the pull request and shows the changes on its lines;
base picks another commit, branch or tag, base-file another file of the repository, and
report one of the three reports:
on: pull_request
jobs:
api:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mykulyak/api-genie-action@v0
with:
command: diff
files: openapi.yaml
Elsewhere, take the base version from git and compare the two files; the exit status fails the job on a breaking change:
git show main:openapi.yaml > main.yaml
apigenie diff main.yaml openapi.yaml
The formats that keep their name: --format markdown, html and json become
--formatter markdown, html and json; text, the default, is stylish, the default. API
Genie adds unix and visualstudio; githubactions is what the action’s annotations do, and
junit, yaml, singleline and markup have no counterpart. A script reads the status alone:
apigenie diff --report status --formatter json main.yaml openapi.yaml
{
"formatVersion": 1,
"status": "breaking"
}
Release notes
oasdiff changelog v1.yaml v2.yaml --format markdown becomes
apigenie diff --formatter markdown v1.yaml v2.yaml, the changelog of two files. For the notes
of a release, apigenie changelog reads the versions of the file
from git instead, with an entry per commit that changes the API or, with --revisions tags, per
release tag, each change rated with the same rules.
What stays with oasdiff
oasdiff diffprints every difference as YAML or JSON, without a severity.--report detailed --formatter jsonis the nearest: every change with its position, the rule and the severity.oasdiff summarycounts the differences by kind;--report statussays only whether a change is breaking.flatten,upgrade,breaking-filesand the git diff driver have no counterpart;validateisapigenie lint.--composed, which compares every file matching two globs as one API, has no counterpart: compare the files one pair at a time, or bundle each version first.--langtranslates the messages; API Genie’s reports are in English.
Remove oasdiff
When the two reports agree on what matters to you: delete .oasdiff.yaml and the ignore files,
remove the oasdiff binary or image from the pipeline, and replace its step with the one above.
Before the next release, let the differ write the version the changes call for:
apigenie diff --update-version main.yaml openapi.yaml