$ npx swagger-cli validate openapi.yaml
Error resolving $ref pointer "openapi.yaml#/components/schemas/Ordr".
Token "Ordr" does not exist.
That spec is valid YAML. It parses. It renders in Swagger UI. And it's broken — a one-character typo (Ordr for Order) in a $ref that no editor flagged, because YAML doesn't know your schema names. This is the whole reason to validate: the file being well-formed tells you nothing about whether it describes a working API. A missing required response, an ambiguous path parameter, or a stale example is exactly what code generation and downstream clients will faithfully turn into someone else's 3am page.
There are four distinct checks, and they catch different failures:
| Check | Catches | Tool example |
|---|---|---|
| Structural validation | Broken $refs, malformed schemas, invalid parameters | swagger-cli, redocly lint |
| Linting | Missing descriptions, inconsistent casing, undocumented errors | Spectral, Redocly rulesets |
| Breaking-change diff | Removed fields/endpoints, narrowed enums, newly required inputs | oasdiff |
| Contract tests | Server responses that drift from the documented schema | Dredd, Schemathesis |
Structural validation is table stakes and belongs in CI on every push. The one people skip is the breaking-change diff — and it's the one that saves you, because a string field becoming integer won't fail structural validation but will shatter every generated client.
responses:
"201":
description: Order created
content:
application/json:
schema: { $ref: "#/components/schemas/Order" }
"422":
description: Validation failed
A schema that says
stringis not documentation for a timestamp, a money amount, or an ID format. Addformat,example, and constraints wherever a client makes a real decision from the field — that's the difference between a spec a generator can use and one a human has to guess at.
Validation vs linting — not the same job
These get conflated because both "check the spec," but they fail on different things and you want both.
Validation answers is this a legal OpenAPI document? It's binary. A broken $ref, a type that isn't a real type, a response object missing its description — the file is either conformant or it isn't, and the spec's own JSON Schema decides.
Linting answers is this a good API contract by our rules? It's opinionated and configurable. "Every operation needs an operationId," "error responses must reference the shared Problem schema," "path segments are kebab-case." A spec can pass validation and fail linting hard, and that's the point — linting encodes the conventions your generated SDKs and your consumers rely on.
FAQ
Is Swagger the same as OpenAPI?
The specification is OpenAPI. "Swagger" is the older name plus the tooling brand (Swagger UI, Swagger Editor, swagger-cli) that SmartBear kept. Swagger 2.0 is OpenAPI 2.0; from 3.0 on it's "OpenAPI." If someone hands you a "Swagger file," check the top: swagger: "2.0" is the old version, openapi: "3.0.3" or openapi: "3.1.0" is current.
Does OpenAPI 3.1 use JSON Schema?
Yes, and it's the headline change. 3.1 is fully aligned with JSON Schema 2020-12, so $ref, oneOf, if/then, and other keywords behave the way JSON Schema tooling expects. 3.0 used a divergent subset, which is why some 3.0 tools choke on 3.1 files — pin your validator to the version you actually author.
How do I catch breaking changes automatically?
Diff the proposed spec against the released one in CI with a tool like oasdiff, and fail the pull request on a breaking-level change: removed endpoint, removed response field, a widened-to-narrowed enum, a new required request property. This turns "we broke the mobile client" from a production incident into a red check.
Treat the contract as a release artifact, not an aspirational diagram — validated in CI, diffed against the last release, and shipped alongside the deployment it describes. A Swagger/OpenAPI validator is the fastest way to catch the structural class before any of this reaches a reviewer.
Cover photo by Stanislav Kondratiev on Pexels.
