DEV Community

Cover image for Linting OpenAPI with Spectral (Plus an Easier Way)
Hassann
Hassann

Posted on • Originally published at apidog.com

Linting OpenAPI with Spectral (Plus an Easier Way)

Two engineers ship two endpoints in the same week. One returns created_at; the other returns createdAt. One paginates with ?page=; the other with ?offset=. Both endpoints may be valid OpenAPI, but together they create an API that feels inconsistent and costs every client extra work. A validator will not catch this because the spec is legal. A linter will.

Try Apidog today

That is the gap Spectral fills. A validator answers: “Is this valid OpenAPI?” A linter answers: “Does this follow the rules our team agreed on?” Spectral is Stoplight’s open-source linter for JSON and YAML documents. It includes OpenAPI rules, supports custom rules, and runs from the CLI, editor, or CI.

This guide shows how to use Spectral to enforce API style rules, then compares that workflow with Apidog, where design, consistency checks, mocks, docs, and tests live in one workspace.

What Spectral actually does

Spectral is a generic linter for structured documents. You give it:

  1. A JSON or YAML document
  2. A ruleset
  3. A command to run

It reports every rule violation with a location, severity, rule name, and message.

Spectral supports OpenAPI, AsyncAPI, and Arazzo out of the box, but you can also lint any JSON or YAML file with your own rules.

Spectral linting output

For API teams, the important ruleset is:

extends: ["spectral:oas"]
Enter fullscreen mode Exit fullscreen mode

The built-in spectral:oas ruleset checks common OpenAPI quality issues, such as:

  • Missing operationId
  • Missing info.description
  • Missing info.contact
  • Undefined tags
  • Duplicate parameters
  • Inconsistent schema patterns

These issues may not break Swagger UI or SDK generation, but they make the API harder to maintain.

Structural validation and linting are different checks:

Check Question it answers
OpenAPI validation Is this document legal OpenAPI?
Spectral linting Does this document follow our API style guide?

Use both. For structural validation, see the guide on how to validate OpenAPI specs. This article focuses on linting for style and consistency.

Install Spectral

Spectral ships as an npm package:

npm install -g @stoplight/spectral-cli
Enter fullscreen mode Exit fullscreen mode

If you do not want a global install, use npx:

npx @stoplight/spectral-cli lint openapi.yaml
Enter fullscreen mode Exit fullscreen mode

Node.js is the only dependency, so Spectral is easy to run locally or in CI.

Add a Spectral ruleset

Create a .spectral.yaml file in your project root:

# .spectral.yaml
extends: ["spectral:oas"]
Enter fullscreen mode Exit fullscreen mode

Now run Spectral against your OpenAPI file:

spectral lint openapi.yaml
Enter fullscreen mode Exit fullscreen mode

Or pass the ruleset explicitly:

spectral lint openapi.yaml --ruleset .spectral.yaml
Enter fullscreen mode Exit fullscreen mode

Example output:

openapi.yaml
  3:6   warning  info-contact      Info object should contain `contact` object.
  5:10  error    info-description  OpenAPI object info `description` must be present.

✖ 2 problems (1 error, 1 warning, 0 infos, 0 hints)
Enter fullscreen mode Exit fullscreen mode

Each finding includes:

  • File location
  • Severity
  • Rule name
  • Human-readable message

Run this once against an existing spec and you will usually find drift immediately.

Write custom Spectral rules

The built-in ruleset is useful, but the real value is encoding your team’s conventions.

A Spectral rule usually defines:

Field Purpose
given JSONPath expression selecting what to inspect
then Function to run against the selected value
severity error, warn, info, or hint
message Output shown when the rule fails

Here is a custom rule that enforces kebab-case paths:

# .spectral.yaml
extends: ["spectral:oas"]

rules:
  paths-kebab-case:
    description: Paths should be kebab-case.
    message: "{{property}} should be kebab-case (lower-case, hyphen-separated)"
    severity: warn
    given: $.paths[*]~
    then:
      function: pattern
      functionOptions:
        match: "^(\\/|[a-z0-9-.]+|{[a-zA-Z0-9_]+})+$"
Enter fullscreen mode Exit fullscreen mode

This rule checks every path key under $.paths.

For example, this path passes:

paths:
  /user-profiles/{userId}:
    get:
      summary: Get a user profile
Enter fullscreen mode Exit fullscreen mode

This path fails:

paths:
  /UserProfiles/{userId}:
    get:
      summary: Get a user profile
Enter fullscreen mode Exit fullscreen mode

You can use the same pattern to enforce rules such as:

  • Require UUID path parameters
  • Require 4xx error responses on every operation
  • Ban version numbers in server URLs
  • Require descriptions on every operation
  • Enforce naming conventions for schemas
  • Require examples on request and response bodies

Spectral includes built-in functions such as:

  • truthy
  • pattern
  • schema
  • length
  • enumeration

For more advanced logic, you can write custom JavaScript or TypeScript functions. That is powerful, but it also means your ruleset becomes code you need to maintain. For a deeper walkthrough, see custom Spectral rules with TypeScript.

Control severity and fail builds

Every Spectral rule has a severity:

error
warn
info
hint
Enter fullscreen mode Exit fullscreen mode

By default, the CLI exits with a non-zero status only when it finds an error.

That means this command may still pass CI if it only finds warnings:

spectral lint openapi.yaml
Enter fullscreen mode Exit fullscreen mode

To fail the build on warnings, use --fail-severity:

spectral lint openapi.yaml --fail-severity=warn
Enter fullscreen mode Exit fullscreen mode

Now both error and warn findings return exit code 1.

This is how you turn Spectral from a reporting tool into a quality gate.

You can also override rule severity in .spectral.yaml:

extends: ["spectral:oas"]

rules:
  operation-operationId: error
  info-contact: off
Enter fullscreen mode Exit fullscreen mode

Use this pattern when adopting Spectral on an existing API:

  1. Start with warnings.
  2. Fix the current backlog.
  3. Raise important rules to errors.
  4. Fail CI on warn or error.

Run Spectral in GitHub Actions

A linter is only useful if it runs automatically. Add Spectral to CI so every pull request checks the spec the same way.

Example GitHub Actions workflow:

name: Lint OpenAPI

on:
  pull_request:
    paths:
      - "openapi.yaml"

jobs:
  spectral:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "20"

      - run: npm install -g @stoplight/spectral-cli

      - run: spectral lint openapi.yaml --fail-severity=warn
Enter fullscreen mode Exit fullscreen mode

If your repo has multiple specs, lint them together:

spectral lint "apis/**/*.yaml" --fail-severity=warn
Enter fullscreen mode Exit fullscreen mode

For CI dashboards, emit JUnit XML:

spectral lint openapi.yaml -f junit -o results.xml
Enter fullscreen mode Exit fullscreen mode

Most CI platforms can parse JUnit output and show failures in a structured test report.

For a broader API quality pipeline, combine:

  1. OpenAPI validation
  2. Spectral linting
  3. Breaking-change detection
  4. Contract tests against the running API

That is the core idea behind treating your API spec as code.

Where Spectral asks more from your team

Spectral is excellent at linting, but it is intentionally focused. Once you adopt it beyond a demo, there are a few operational costs.

You own the ruleset

spectral:oas is generic. Your real API style guide lives in custom rules.

That means you need to:

  • Write the rules
  • Review changes
  • Version the ruleset
  • Update rules as conventions change
  • Teach the team JSONPath and Spectral functions

For larger teams, the ruleset becomes a small internal codebase.

It lints the spec, not the running API

Spectral reads the OpenAPI file. It does not send requests to your service.

A spec can pass every Spectral rule and still be wrong if the implementation has drifted.

To close that gap, you need API tests that execute real requests and assert responses against expected behavior.

It is one tool in a longer chain

After linting, many teams still need:

  • Mock servers
  • Interactive docs
  • API debugging
  • Contract testing
  • Automated test scenarios
  • CI reporting

Spectral does not manage those pieces. You need to wire them together and keep them aligned around the same spec.

That is not a weakness of Spectral. It is a focused linter. But the integration work belongs to you.

The easier path: consistency inside the design workflow

Another approach is to move consistency checks into the place where the API is designed.

Apidog is an all-in-one API platform where you can:

  • Design API schemas
  • Debug requests
  • Build test scenarios
  • Mock endpoints
  • Publish API docs
  • Run tests from CI

Because the contract is created inside the workspace, consistency checks happen during design instead of after the spec is committed. The editor surfaces structural issues while you work, so the feedback loop is shorter.

The downstream workflow also uses the same source of truth. The contract you design can power mocks, docs, and test scenarios without moving between separate tools.

For CI, the Apidog CLI runs API test scenarios headlessly:

npm install -g apidog-cli

apidog run --access-token $APIDOG_ACCESS_TOKEN -t <scenarioId> -e <environmentId> -r cli
Enter fullscreen mode Exit fullscreen mode

This gives you a different kind of gate:

  • Spectral checks whether the document follows style rules.
  • Apidog CLI checks whether the running API behaves as expected.

For CLI options, run:

apidog run --help
Enter fullscreen mode Exit fullscreen mode

Or read the complete Apidog CLI guide.

The trade-off is straightforward:

Option Best when
Spectral You want a free, portable, scriptable OpenAPI linter
Apidog You want design, consistency, mocks, docs, and tests in one workflow

Spectral vs Apidog at a glance

Capability Spectral Apidog
OpenAPI style linting Yes, via spectral:oas and custom rules Yes, surfaced live in the designer
Custom rules Yes, YAML or JS/TS; you maintain them Conventions enforced by the editor, no rule code
Structural validation Use with Redocly or another validator Built in at design time
Mock server No Yes
Auto-generated docs No Yes
Runnable API tests No Yes, via the Apidog CLI
CI gate spectral lint --fail-severity=warn apidog run non-zero exit
Cost Free, open source Free tier, paid plans

Use this as a decision aid, not a scoreboard. Pick the tool that matches how much of the API lifecycle you want one system to own.

Frequently asked questions

Is Spectral free?

Yes. Spectral is open source under the Apache 2.0 license. The CLI, built-in OpenAPI ruleset, and custom-rule support are free to use.

Does Spectral validate that my spec is legal OpenAPI?

Partly. Spectral catches many structural issues, but it is a linter, not a dedicated schema validator.

For full structural validation, pair it with an OpenAPI validator. See validating OpenAPI specs and the comparison of best OpenAPI validator tools.

Can Spectral test my running API?

No. Spectral reads the spec file only.

To test the live API, use a runner that sends real requests and asserts on responses, such as the Apidog CLI.

How do I make a Spectral warning fail my CI build?

Use:

spectral lint openapi.yaml --fail-severity=warn
Enter fullscreen mode Exit fullscreen mode

By default, only error fails the build. --fail-severity=warn makes warnings fail too.

What is the difference between Spectral and Apidog?

Spectral is a focused open-source linter that you configure and maintain.

Apidog is an all-in-one API platform where design, consistency checks, mocking, docs, and testing live together. For a related comparison, see Apidog vs Swagger.

Wrapping up

Spectral solves a real problem: it enforces API consistency rules that basic OpenAPI validators ignore.

A practical setup looks like this:

npm install -g @stoplight/spectral-cli
Enter fullscreen mode Exit fullscreen mode
# .spectral.yaml
extends: ["spectral:oas"]
Enter fullscreen mode Exit fullscreen mode
spectral lint openapi.yaml --fail-severity=warn
Enter fullscreen mode Exit fullscreen mode

Then add custom rules for your team’s style guide and run the command in CI.

For many teams, that is enough.

The cost appears when you need to maintain custom rules and connect linting to the rest of the API lifecycle. If you want consistency checks, mocks, docs, and runnable tests from one source of truth, download Apidog and build the spec where those checks are already part of the workflow.

Either way, the goal is the same: an API contract your team can trust because machines enforce it consistently.

Top comments (0)