# Migrate from oasdiff

Source: https://apigenie.pl/docs/guides/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](/docs/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](/docs/reference/cli/diff/rules#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](/docs/reference/cli/diff/rules)) |
| Configuration | `.oasdiff.yaml`, `--severity-levels`, `--err-ignore`, `--warn-ignore` | `apigenie.yaml`, section `commands.diff` ([Configuration](/docs/reference/cli/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](/docs/reference/cli/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.

```sh
oasdiff breaking v1.yaml v2.yaml
apigenie diff v1.yaml v2.yaml
```

```text
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`
```

```text
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](/docs/reference/cli/diff/rules#openapi) 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:

```yaml
version: '1.0'
commands:
  diff:
    extends: [recommended]
    rules:
      info:
        title: non_functional
    response:
      schema:
        properties:
          remove_item: non_breaking
```

- `--fail-on ERR` has no flag: the command exits with 1 on a breaking change. oasdiff's warnings
  are changes that may break a client, such as `request-parameter-removed`; the recommended rules
  rate most of them breaking already, so `--fail-on WARN` is the default exit status too. A change
  rated too low or too high gets another severity in `rules`, or in `request` and `response` for
  the schemas of parameters and bodies and those of responses
  ([direction](/docs/reference/cli/diff/rules#direction)).
- `--severity-levels`, `--err-ignore` and `--warn-ignore` match the lines of a report; a rule
  names the property instead. `none` leaves a change out of the status and the changelog,
  `non_functional` keeps it in the changelog without failing the check.
- `--match-path`, `--unmatch-path` and `--filter-extension` become `overrides`, 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:

  ```yaml
    overrides:
      - where:
          location: /paths/~1internal~1*/**
        rules:
          pathItem:
            operations: none
  ```

- `--exclude-elements description,summary,examples,extensions` is close to the default:
  descriptions, summaries, examples and extensions are `non_functional`, in the changelog and never
  failing. `none` on the property, `operation: {description: none}`, leaves them out.
- `--auto-upgrade` has 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 the `openapi` rule of the `openapi` node;
  `none` on it leaves it out.
- `--flatten-allof`, `--flatten-params`, `--include-path-params`, `--composed`, `--prefix-base`,
  `--strip-prefix-base`, `--stability-level`, `--attributes`, `--lang` and `--template` have 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-stable` or
  `--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_passed` condition: a deprecated operation, path item, parameter, component schema,
  response property or request enum value may go once its `x-sunset` date 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`](/docs/reference/cli/evolve) writes the deprecations and the removal
in two waves. [Accepting a breaking change](/docs/reference/cli/diff/rules#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](/docs/reference/cli/diff/rules#openapi), 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](/docs/ci/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:

```yaml
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:

```sh
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:

```sh
apigenie diff --report status --formatter json main.yaml openapi.yaml
```

```json
{
  "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`](/docs/reference/cli/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 diff` prints every difference as YAML or JSON, without a severity.
  `--report detailed --formatter json` is the nearest: every change with its position, the rule
  and the severity.
- `oasdiff summary` counts the differences by kind; `--report status` says only whether a change
  is breaking.
- `flatten`, `upgrade`, `breaking-files` and the git diff driver have no counterpart;
  `validate` is [`apigenie lint`](/docs/reference/cli/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](/docs/reference/cli/bundle) each version
  first.
- `--lang` translates 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:

```sh
apigenie diff --update-version main.yaml openapi.yaml
```
