# Migrate from Redocly CLI

Source: https://apigenie.pl/docs/guides/migrate-from-redocly

This guide moves a repository that lints and bundles its OpenAPI descriptions with Redocly CLI to
`apigenie`: the ruleset and the rules by name, `redocly.yaml`, your configurable rules, the
decorators, the bundle, the report in CI, and what stays with Redocly. It starts from Redocly CLI
2.72 with its `recommended` ruleset and an OpenAPI file, `openapi.yaml`.

You need the CLI installed; the [Quickstart](/docs/quickstart) shows how. Keep Redocly CLI
installed until the last step, so you can compare the two reports on the same files.

## What changes and what stays

| | Redocly CLI | API Genie |
| --- | --- | --- |
| Lint | `redocly lint openapi.yaml` | `apigenie lint openapi.yaml` |
| Configuration | `redocly.yaml`: `extends`, `rules`, `apis` | `apigenie.yaml`, section `commands.lint` ([Configuration](/docs/reference/cli/configuration)) |
| Built-in rulesets | `minimal`, `recommended`, `recommended-strict`, `all`, `spec` | `recommended`, `strict`, `all`, per standard; 64 rules for [OpenAPI](/docs/linter/openapi) |
| Severities | `error`, `warn`, `off` | `error`, `warning`, `hint`, `off` |
| Configurable rules | `rule/<name>` with `subject` and `assertions` | custom rules in Spectral's format, `given` and `then` |
| Plugins | JavaScript rules, decorators and preprocessors | JavaScript functions, in a sandbox |
| Ignoring a problem | `.redocly.lint-ignore.yaml` | `x-apigenie-lint-disable` in the document |
| Decorators | `remove-x-internal`, `info-override`, `filter-in`, ... | OpenAPI Overlays, applied by `apigenie overlay` |
| Bundle | `redocly bundle openapi.yaml -o dist/openapi.yaml` | `apigenie bundle openapi.yaml --output dist/openapi.yaml` |
| Report formats | `--format codeframe`, `stylish`, `json`, `junit`, ... | `--formatter stylish`, `json`, `junit`, `sarif`, ... |
| Exit status | 1 on an error, 0 on warnings alone | the same, and 2 on a file it cannot read |
| Fixes | none | `--fix` for the issues that have one |
| Configuration check | `redocly check-config` | `apigenie config check` |
| Documentation | `redocly build-docs openapi.yaml` | `apigenie-generator docs openapi.yaml -o docs` |

## Run both on the same file

```sh
redocly lint --format stylish openapi.yaml
apigenie lint openapi.yaml
```

With its `recommended` ruleset, Redocly reports three problems in a small file whose operation
has no security requirement, no error response and an unused schema:

```text
openapi.yaml:
  15:5  error    security-defined        Every operation should have security defined on it or on the root level.
  19:7  warning  operation-4xx-response  Operation must have at least one `4XX` response.
  33:5  warning  no-unused-components    Component: "Unused" is never used.
```

API Genie reports ten:

```text
   3:3   error    Info summary is missing or empty                                  info-defined
   3:3   error    Info description is missing or empty                              info-defined
  11:5   warning  Tag 'pets' has no meaningful description defined                  tag-description-defined
  16:7   error    Operation has no meaningful description                           operation-description-defined
  16:7   warning  Security requirement is not defined                               operation-security-defined
  21:11  warning  Missing rate limiting header                                      operation-rate-limiting-defined
  27:19  error    Object schema does not disable additional properties              schema-no-additional-properties
  30:23  error    Schema with integer type does not define proper format            schema-format-defined
  34:7   warning  String schema does not define constraints restricting its length  schema-constraints
  34:7   warning  Unused shared component                                           unused-shared-component

  10 issues (5 errors, 5 warnings)
  1 errors, 1 warnings are potentially auto-fixable
```

Each line gives the position, the severity, the message and the rule; every rule has a page in the
[linter rules](/docs/linter) that says what it checks and why. The two reports differ, and not
only in the names: without configuration API Genie applies the
[recommended ruleset](/docs/linter/openapi/rulesets/recommended), 43 rules that ask for
descriptions, formats and bounds Redocly's `recommended` does not, and it leaves out some of what
Redocly reports, such as the missing error response. The next two sections say which.

## Replace the configuration

A `redocly.yaml` that extends the built-in ruleset, adjusts a few rules and names the APIs:

```yaml
extends:
  - recommended
rules:
  operation-4xx-response: off
  no-unused-components: error
  info-contact: warn
apis:
  orders@v1:
    root: apis/orders/openapi.yaml
```

becomes, in `apigenie.yaml` at the root of the repository:

```yaml
version: '1.0'
commands:
  lint:
    extends: [recommended]
    rules:
      unused-shared-component: error
      contact-defined: warning
```

- `extends` names API Genie's rulesets, not Redocly's. `recommended` is the default; `strict`
  turns on more rules with higher severities, `all` every rule. Redocly's `recommended-strict` is
  `recommended` with every rule at `error`, which has no ruleset of its own: start from
  `recommended` and raise the rules that matter to `error`. `minimal` is `recommended` with most
  rules off, and `spec`, the `struct` rule alone, is
  [openapi-json-schema-validation](/docs/linter/openapi/rules/openapi-json-schema-validation)
  listed under `rules` without `extends`.
- A rule takes its name from the tables below. Redocly's `warn` is `warning`; `error` and `off`
  stay.
- A rule with no counterpart, such as `operation-4xx-response`, has no line.
- `apis` has no counterpart: one `commands.lint` section applies to every file the command is
  given. A repository whose APIs need different rules keeps a configuration file per API and
  passes it with `--config`, or disables rules in the documents, as [below](#disable-a-rule-in-one-place).
- `oas2Rules`, `oas3_0Rules`, `oas3_1Rules` and `async3Rules` become the `swagger` and `asyncapi`
  sections next to `rules`: an OpenAPI 3.x file, a Swagger 2.0 file and an AsyncAPI file are
  linted by three linters with rules of their own, from one configuration
  ([configuration of lint](/docs/reference/cli/lint#configuration)). `arazzo1Rules` have no
  counterpart: Arazzo files are not read.
- `--skip-rule` on the command line is `off` in the configuration, and `--extends` is `--config`
  with another file. `--max-problems` has no counterpart.

## The rules of recommended, by name

The rules Redocly's `recommended` ruleset reports as errors, and their counterparts in the OpenAPI
linter. A rule marked *not checked* has no counterpart today: drop its line from the
configuration.

| Redocly | API Genie | Note |
| --- | --- | --- |
| `struct` | [openapi-json-schema-validation](/docs/linter/openapi/rules/openapi-json-schema-validation) | |
| `no-unresolved-refs` | [openapi-json-schema-validation](/docs/linter/openapi/rules/openapi-json-schema-validation) | local schema `$ref` targets are checked; external references are not fetched |
| `operation-operationId-unique` | [operation-id-unique](/docs/linter/openapi/rules/operation-id-unique) | |
| `operation-parameters-unique` | [parameters-unique](/docs/linter/openapi/rules/parameters-unique) | |
| `path-parameters-defined` | [path-parameters-defined](/docs/linter/openapi/rules/path-parameters-defined) | [path-parameters-on-items](/docs/linter/openapi/rules/path-parameters-on-items) asks for them on the path item |
| `security-defined` | [operation-security-defined](/docs/linter/openapi/rules/operation-security-defined) | every operation has a security requirement |
| `no-identical-paths` | [paths-unique](/docs/linter/openapi/rules/paths-unique) | |
| `no-path-trailing-slash`, `path-declaration-must-exist` | [paths-well-formed](/docs/linter/openapi/rules/paths-well-formed) | also rejects `//` and upper-case letters outside the placeholders |
| `path-not-include-query` | not checked | |
| `no-enum-type-mismatch` | [schema-enum-types](/docs/linter/openapi/rules/schema-enum-types) | |
| `no-schema-type-mismatch`, `no-example-value-and-externalValue` | not checked | |
| `no-empty-servers` | [servers-defined](/docs/linter/openapi/rules/servers-defined) | |
| `no-server-trailing-slash`, `no-server-variables-empty-enum`, `no-undefined-server-variable` | not checked | [server-urls](/docs/linter/openapi/rules/server-urls) checks the scheme and the case of the URL instead |
| `operation-summary` | not checked | the custom rule [below](#carry-your-configurable-rules-over) |
| `operation-operationId-url-safe`, `spec-components-invalid-map-name` | not checked | |

The rules it reports as warnings:

| Redocly | API Genie | Note |
| --- | --- | --- |
| `info-license`, `info-license-strict` | [license-defined](/docs/linter/openapi/rules/license-defined) | asks for `name`, and `url` or `identifier` |
| `no-ambiguous-paths` | [paths-unique](/docs/linter/openapi/rules/paths-unique) | |
| `no-duplicated-enum-values` | [schema-enum-no-duplicates](/docs/linter/openapi/rules/schema-enum-no-duplicates) | |
| `no-duplicated-tag-names` | [tags-unique](/docs/linter/openapi/rules/tags-unique) | |
| `no-required-schema-properties-undefined` | [schema-required-properties-defined](/docs/linter/openapi/rules/schema-required-properties-defined) | [schema-no-contradictions](/docs/linter/openapi/rules/schema-no-contradictions) adds the case of `additionalProperties: false` |
| `no-unsafe-markdown` | [safe-markdown](/docs/linter/openapi/rules/safe-markdown) | |
| `no-unused-components` | [unused-shared-component](/docs/linter/openapi/rules/unused-shared-component) | follows references transitively, so a component used only by an unused one is reported too |
| `operation-2xx-response` | [operation-success-response](/docs/linter/openapi/rules/operation-success-response) | |
| `operation-4xx-response` | not checked | [standard-response-errors](/docs/linter/openapi/rules/standard-response-errors) checks the format of the error responses that are there |
| `operation-operationId` | [operation-id-defined](/docs/linter/openapi/rules/operation-id-defined) | |
| `tag-description` | [tag-description-defined](/docs/linter/openapi/rules/tag-description-defined) | |
| `no-invalid-media-type-examples`, `no-invalid-parameter-examples`, `no-invalid-schema-examples` | not checked | examples are not validated against their schema |
| `no-illogical-composition-keywords`, `no-mixed-number-range-constraints` | not checked | [schema-no-contradictions](/docs/linter/openapi/rules/schema-no-contradictions) reports a schema that rejects every value, [schema-discriminator-defined](/docs/linter/openapi/rules/schema-discriminator-defined) a `oneOf` without discriminator |
| `no-server-example.com`, `security-scopes-defined`, `spec-ref-siblings` | not checked | |

The rules Redocly leaves off by default:

| Redocly | API Genie |
| --- | --- |
| `info-contact` | [contact-defined](/docs/linter/openapi/rules/contact-defined) |
| `operation-description` | [operation-description-defined](/docs/linter/openapi/rules/operation-description-defined) |
| `parameter-description` | [parameter-description-defined](/docs/linter/openapi/rules/parameter-description-defined) |
| `path-params-defined` | [path-parameters-defined](/docs/linter/openapi/rules/path-parameters-defined) |
| `tags-alphabetical` | [tags-sorted](/docs/linter/openapi/rules/tags-sorted) |
| `request-mime-type`, `response-mime-type` | [standard-media-types](/docs/linter/openapi/rules/standard-media-types), which checks that the media types are registered ones rather than a list of yours |
| `required-string-property-missing-min-length` | [schema-constraints](/docs/linter/openapi/rules/schema-constraints) |
| `scalar-property-missing-example` | [schema-example-defined](/docs/linter/openapi/rules/schema-example-defined) |
| `operation-4xx-problem-details-rfc7807` | [standard-response-errors](/docs/linter/openapi/rules/standard-response-errors) |
| `response-contains-header` | not checked; [operation-rate-limiting-defined](/docs/linter/openapi/rules/operation-rate-limiting-defined) asks for the rate-limiting headers |
| `component-name-unique`, `no-http-verbs-in-paths`, `operation-singular-tag`, `operation-tag-defined`, `paths-kebab-case`, `path-http-verbs-order`, `path-segment-plural`, `boolean-parameter-prefixes`, `array-parameter-serialization`, `response-contains-property`, `spec-strict-refs`, `spec-example-values` | not checked |

The `oas2Rules` apply to Swagger 2.0 files, which API Genie lints with the
[Swagger 2.0 linter](/docs/linter/swagger): `struct` is
[swagger-validation](/docs/linter/swagger/rules/swagger-validation), which also reports the
unresolved references, and the rules that share a name with an OpenAPI rule check the same thing.
The `async3Rules` map onto the [AsyncAPI linter](/docs/linter/asyncapi) the same way.

## What the recommended ruleset adds

Rules of `recommended` that Redocly's has nothing for; each has a page that says what it checks
and why:

- [info-defined](/docs/linter/openapi/rules/info-defined),
  [operation-description-defined](/docs/linter/openapi/rules/operation-description-defined),
  [parameter-description-defined](/docs/linter/openapi/rules/parameter-description-defined) and
  [tag-description-defined](/docs/linter/openapi/rules/tag-description-defined): the texts a
  reader of the documentation needs, and they have to say something.
- [schema-format-defined](/docs/linter/openapi/rules/schema-format-defined),
  [schema-format-type-mismatch](/docs/linter/openapi/rules/schema-format-type-mismatch) and
  [schema-format-deprecated](/docs/linter/openapi/rules/schema-format-deprecated): numbers and
  strings say their format, and the format fits the type.
- [schema-constraints](/docs/linter/openapi/rules/schema-constraints) and
  [schema-no-additional-properties](/docs/linter/openapi/rules/schema-no-additional-properties):
  strings, numbers, arrays and objects have bounds.
- [schema-no-contradictions](/docs/linter/openapi/rules/schema-no-contradictions),
  [schema-oneof-no-overlap](/docs/linter/openapi/rules/schema-oneof-no-overlap) and
  [schema-conditional-effective](/docs/linter/openapi/rules/schema-conditional-effective): schemas
  that reject every value, `oneOf` branches a value matches twice, `if` without effect.
- [standard-response-codes](/docs/linter/openapi/rules/standard-response-codes),
  [unambiguous-response-codes](/docs/linter/openapi/rules/unambiguous-response-codes) and
  [no-content-response-defined](/docs/linter/openapi/rules/no-content-response-defined): the
  responses an operation declares.
- [operation-rate-limiting-defined](/docs/linter/openapi/rules/operation-rate-limiting-defined),
  [paths-max-depth](/docs/linter/openapi/rules/paths-max-depth),
  [paths-max-count](/docs/linter/openapi/rules/paths-max-count),
  [parameter-naming](/docs/linter/openapi/rules/parameter-naming) and
  [schema-datetime-suffix](/docs/linter/openapi/rules/schema-datetime-suffix): the headers, the
  shape of the URLs and the names.

Outside `recommended`: [spell-check](/docs/linter/openapi/rules/spell-check) for the texts written
for humans, [schema-keyword-support](/docs/linter/openapi/rules/schema-keyword-support) for the
JSON Schema keywords the tools around the document ignore, and the
[privacy ruleset](/docs/linter/openapi/rulesets/privacy) for credentials and personal data in
URLs. `extends: [recommended, privacy]` adds the last one.

## Carry your configurable rules over

A configurable rule of `redocly.yaml` names the nodes it checks with `subject` and what it asserts
about them:

```yaml
rules:
  rule/operation-summary:
    subject:
      type: Operation
      property: summary
    assertions:
      defined: true
    severity: error
    message: Operations must have a summary
```

It becomes a [custom rule](/docs/reference/cli/lint#custom-rules) under `commands.lint.rules`,
in the format of Spectral: a JSONPath expression selects the nodes, a function checks each of
them:

```yaml
version: '1.0'
commands:
  lint:
    extends: [recommended]
    rules:
      operation-summary:
        description: Operations must have a summary
        message: "{{property}} is missing at {{path}}"
        severity: error
        given: "$.paths[*][get,put,post,delete,options,head,patch,trace]"
        then:
          field: summary
          function: truthy
```

- `subject.type` becomes the `given` expression that selects the same nodes, `subject.property`
  the `field`, and `subject.filterInParentKeys` a member union in the expression. A `where`
  condition becomes a JSONPath filter ([JSONPath](/docs/reference/cli/lint#jsonpath)).
- The assertions map onto the [core functions](/docs/reference/cli/lint#core-functions):
  `defined` is `truthy` or `defined`, `pattern` and `notPattern` are `pattern` with `match` or
  `notMatch`, `enum` is `enumeration`, `casing` is `casing`, `minLength` and `maxLength` are
  `length`, `mutuallyExclusive` is `xor`, `sortOrder` is `alphabetical`. `required`, `disallowed`,
  `requireAny`, `mutuallyRequired`, `nonEmpty` and `ref` have no function: write a
  [JavaScript function](/docs/reference/cli/lint#javascript-functions) of a few lines.
- `severity` and `message` stay; `{{property}}`, `{{path}}` and `{{value}}` fill the message.

A rule of a custom plugin is a JavaScript visitor over the document's node types; it is rewritten
as a custom rule whose function is a JavaScript module in `functions/` next to `apigenie.yaml`.
Functions run in a sandbox with no file system, network, processes or timers, and a call must
finish within 5 seconds. The decorators and preprocessors of a plugin become overlays, next.

## Replace the decorators with overlays

Decorators change the document on the way to the bundle and the documentation: `remove-x-internal`
drops what is marked internal, `info-override` and `filter-in` or `filter-out` edit and select.
API Genie writes these changes as an [OpenAPI Overlay](/docs/reference/cli/overlay), a file of
actions that each select nodes with a JSONPath query and change them:

```yaml
overlay: 1.0.0
info:
  title: Remove the internal operations
  version: 1.0.0
actions:
  - target: $.paths[*][?(@["x-internal"] == true)]
    remove: true
  - target: $.paths[?(@["x-internal"] == true)]
    remove: true
  - target: $.info
    update:
      title: Public API
      description: The public operations only.
```

```sh
apigenie overlay --overlay public.overlay.yaml openapi.yaml --output dist/openapi.yaml
```

The first two actions remove the operations and the path items marked `x-internal: true`, the
third edits `info`; YAML comments of the input are kept. The code generators take an overlay
directly, with `--overlay` or the `overlay` setting of a target
([Overlays](/docs/reference/generator#overlays)), so the public version of the document need not
be written to disk for them.

## Bundle

```sh
redocly bundle openapi.yaml -o dist/openapi.yaml
apigenie bundle openapi.yaml --output dist/openapi.yaml
```

Both copy the schemas, parameters and responses of other local files into `components`, named
after their files, and rewrite the references; the extension of the output file picks JSON or
YAML. [bundle](/docs/reference/cli/bundle) does not keep YAML comments, and references by URL are
not supported. `--dereferenced`, `--remove-unused-components`, `--keep-url-references`, `--ext`
and `--metafile` have no counterpart, and decorators don't run during the bundle: apply the
overlay to the bundled file in a second command.

## Disable a rule in one place

Redocly keeps the problems to ignore in `.redocly.lint-ignore.yaml`, written by
`--generate-ignore-file`. API Genie turns the rule off where the issue is, in the document, with
`x-apigenie-lint-disable` on the object and its descendants:

```yaml
paths:
  /legacy/export:
    x-apigenie-lint-disable:
      - operation-id-defined
    get:
      summary: Export, as it was
```

Disable a rule as close to the issue as possible;
[disabling rules](/docs/reference/cli/lint#disabling-rules-in-a-document) says more.

## The report in CI

The formats that keep their name: `--format stylish`, `json`, `checkstyle` and `junit` become
`--formatter stylish`, `json`, `checkstyle` and `junit`. API Genie adds `sarif`, `html`,
`compact`, `tap`, `unix` and `visualstudio`; Redocly's `codeframe` (its default), `codeclimate`,
`summary`, `markdown` and `github-actions` have no counterpart. For GitLab, `junit` is what the
merge request reads; for GitHub code scanning, `sarif`
([formatters](/docs/reference/cli/lint#formatters)). The exit status stays what it was: 1 when an
error is reported, 0 on warnings alone ([exit status](/docs/reference/cli/lint#exit-status)).

On GitHub Actions, the `run` step with `--format github-actions` becomes the
[GitHub Action](/docs/ci/github-action), which installs the CLI and shows the issues on the lines
of the pull request:

```yaml
on: pull_request
jobs:
  api:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: mykulyak/api-genie-action@v0
        with:
          command: lint
          files: openapi.yaml
```

## What stays with Redocly

- `push`, `project`, `preview`, `login`, `eject` and `translate` work with the Redocly platform and
  its projects. `build-docs` becomes
  [`apigenie-generator docs`](/docs/reference/generator/docs), a static site with search and
  Try it out, built with npm and served from any static host.
- `split` and `join` have no counterpart; `apigenie bundle` goes one way.
- `generate-client` becomes [`apigenie-generator client fetch`](/docs/reference/generator/client-fetch),
  and `diff` becomes [`apigenie diff`](/docs/reference/cli/diff);
  [Migrate from oasdiff](/docs/guides/migrate-from-oasdiff) covers the move of a breaking-change
  check in detail.
- `respect` and `generate-arazzo` run and write Arazzo workflows, which are not read;
  [`apigenie test`](/docs/reference/cli/test) checks a running server against the description
  with tests of its own format.
- `proxy`, `drift` and `generate-spec` work on recorded traffic. The validating proxy of
  [`apigenie-server`](/docs/concepts/what-the-proxy-checks) checks live traffic against the
  description; inferring a description from traffic has no counterpart.
- `stats`, `score` and `scorecard-classic` have no counterpart.

## Remove Redocly CLI

When the two reports agree on what matters to you: delete `redocly.yaml` and
`.redocly.lint-ignore.yaml`, remove `@redocly/cli` from the development dependencies, and replace
the Redocly step of the pipeline with the one above. The fixes the linter offers are a `--fix`
away:

```sh
apigenie lint --fix openapi.yaml
```
