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.
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:
- A JSON or YAML document
- A ruleset
- 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.
For API teams, the important ruleset is:
extends: ["spectral:oas"]
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
If you do not want a global install, use npx:
npx @stoplight/spectral-cli lint openapi.yaml
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"]
Now run Spectral against your OpenAPI file:
spectral lint openapi.yaml
Or pass the ruleset explicitly:
spectral lint openapi.yaml --ruleset .spectral.yaml
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)
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_]+})+$"
This rule checks every path key under $.paths.
For example, this path passes:
paths:
/user-profiles/{userId}:
get:
summary: Get a user profile
This path fails:
paths:
/UserProfiles/{userId}:
get:
summary: Get a user profile
You can use the same pattern to enforce rules such as:
- Require UUID path parameters
- Require
4xxerror 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:
truthypatternschemalengthenumeration
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
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
To fail the build on warnings, use --fail-severity:
spectral lint openapi.yaml --fail-severity=warn
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
Use this pattern when adopting Spectral on an existing API:
- Start with warnings.
- Fix the current backlog.
- Raise important rules to errors.
- Fail CI on
warnorerror.
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
If your repo has multiple specs, lint them together:
spectral lint "apis/**/*.yaml" --fail-severity=warn
For CI dashboards, emit JUnit XML:
spectral lint openapi.yaml -f junit -o results.xml
Most CI platforms can parse JUnit output and show failures in a structured test report.
For a broader API quality pipeline, combine:
- OpenAPI validation
- Spectral linting
- Breaking-change detection
- 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
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
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
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
# .spectral.yaml
extends: ["spectral:oas"]
spectral lint openapi.yaml --fail-severity=warn
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)