How to validate OpenAPI spec

Run npx --yes @redocly/cli@2.52.1 lint openapi.yaml. A structurally valid document ends with a timing line and exit 0. Anything wrong prints one row per problem with a line and column, the rule that fired, and whether it counts as an error or a warning. Exit 1 means the run failed.

Why check this

A spec with an unresolved $ref still renders in a documentation viewer and still fails every client generator that reads it. Contract tests built on that spec then pass against nothing. Lint the file in CI on every pull request that touches it, and again before the spec is published to consumers.

Prerequisites

openapi: 3.1.0
info:
  title: Orders API
  version: 1.4.0
  license:
    name: Apache-2.0
    url: https://www.apache.org/licenses/LICENSE-2.0
servers:
  - url: https://api.orders.internal/v1
security:
  - bearerAuth: []
paths:
  /orders/{id}:
    get:
      operationId: getOrder
      summary: Read one order
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The order
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '404':
          description: No such order
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  schemas:
    Order:
      type: object
      required: [id, total]
      properties:
        id:
          type: string
        total:
          type: number

Steps

  1. Step 1.

    Lint the spec with the ruleset the tool applies when no config file is present.

    npx --yes @redocly/cli@2.52.1 lint --format=stylish openapi.yaml
    
    No configurations were provided -- using built in recommended configuration by default.
    
    validating openapi.yaml...
    openapi.yaml: validated in 27ms
    …

    No problem rows appear between the two lines, which is what a clean run looks like.

  2. Step 2.

    Read the exit code, because that is what a pipeline acts on.

    echo $?
    
    0
  3. Step 3.

    Copy the spec to openapi-badref.yaml, change the $ref on line 29 to #/components/schemas/Ordr, and lint that copy.

    npx --yes @redocly/cli@2.52.1 lint --format=stylish openapi-badref.yaml
    
    validating openapi-badref.yaml...
    openapi-badref.yaml:
    29:17  error    no-unresolved-refs    Can't resolve $ref
    38:5   warning  no-unused-components  Component: "Order" is never used.
    
    openapi-badref.yaml: validated in 35ms
    …

    The second row is the useful one. A component that nothing references is the signature of a typo in a pointer, not of dead code.

  4. Step 4.

    Copy the spec to openapi-broken.yaml, delete the parameters block and the description under the 200 response, then lint it.

    npx --yes @redocly/cli@2.52.1 lint --format=stylish openapi-broken.yaml
    
    validating openapi-broken.yaml...
    openapi-broken.yaml:
    18:9  error    struct                   The field `description` must be present on this level.
    14:5  error    path-parameters-defined  The operation does not define the path parameter `{id}` expected by path `/orders/{id}`.
    
    openapi-broken.yaml: validated in 41ms
    …
  5. Step 5.

    Re-run the same file with the minimal ruleset, which is the one to put in CI while a spec is being cleaned up.

    npx --yes @redocly/cli@2.52.1 lint --extends=minimal --format=stylish openapi-broken.yaml
    
    validating openapi-broken.yaml...
    openapi-broken.yaml:
    18:9  error    struct                   The field `description` must be present on this level.
    14:5  warning  path-parameters-defined  The operation does not define the path parameter `{id}` expected by path `/orders/{id}`.
    
    openapi-broken.yaml: validated in 44ms
    …

    struct stays an error and path-parameters-defined drops to a warning. That difference is the line between "not a valid OpenAPI document" and "valid, but against house style".

  6. Step 6.

    Read the exit code of the failing run.

    echo $?
    
    1

    A warning alone leaves the exit code at 0, so a spec can carry warnings and still pass the gate.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | A timing line with no rows, exit 0 | The document matches the OpenAPI structure | Nothing. Keep the command in the pull request check. | | error struct | A required field is absent from an object | Fix it first. Generators and parsers refuse the file in this state. | | error no-unresolved-refs | A $ref points at a target that is not there | Check the spelling, then check whether a split file was left out. | | warning no-unused-components | A schema is defined and never referenced | Usually a typo in a pointer. Read it together with the row above. | | error path-parameters-defined | The path template names a parameter the operation never declares | Add the parameter. Clients send the path, servers cannot bind the value. | | Rows present, exit 0 | Every row was a warning | Decide whether the rule belongs in the gate, not whether to ignore it. |

Common mistakes

Sign: The spec is called invalid because the linter printed warnings about licences and servers.Cause: The default recommended ruleset mixes structure with style. Only the struct rule decides whether the document is a valid OpenAPI description. Run --extends=minimal to see that split, as step 5 does.
Sign: Swagger Editor shows the spec as fine and the build still fails.Cause: Swagger Editor validates OpenAPI 3.0 semantics and is lenient about 3.1, where the schema dialect changed to JSON Schema 2020-12. Lint the file with the version of the tool your pipeline runs.
Sign: The lint passes and the generated client is missing endpoints.Cause: A spec assembled from several files can lose a path when a $ref resolves to an empty document. Bundle first with redocly bundle, then lint the bundled output, which is what consumers receive.
Sign: A spec exported from a UI passes, the one in the repository does not.Cause: Export tools normalise the document on the way out, adding descriptions and dropping unknown keys. Validate the file that ships, which is the one under version control.

What to check next

FAQ

How to validate a Swagger schema?

Swagger 2.0 files lint with the same command. Redocly CLI reads swagger: '2.0' and applies the rules for that version. Converting to OpenAPI 3.x first is a separate change with its own risk, so validate the file as it is before converting it.

Can I validate an OpenAPI spec online?

Editors such as Swagger Editor parse and report as you type, which suits drafting. They do not run in CI and they do not pin a ruleset, so a file that passes in a browser can fail the build. Use the CLI for the gate.

Does a valid spec mean the API matches it?

No. Linting reads one file and never sends a request. Pair it with a contract test that captures a real response and validates it against the schema the spec declares for that route.

Which problems should fail the build?

Start with errors from the minimal ruleset, which is struct plus unresolved references. Add style rules one at a time, after the existing spec passes them, so the gate never lands on the team as a wall of pre-existing warnings.

Verified

Verified by Maks Verny@redocly/cli 2.52.1node 22.23.2

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

intermediate6 minpublished updated Maks Verny