"Validate the OpenAPI spec" usually means two different checks bundled into one step. The first is structural: is this well-formed YAML or JSON that matches the shape the OpenAPI format defines? The second is semantic: does it also follow the rules a schema can't express on its own — unique operation IDs, path parameters that are actually declared, $refs that resolve to something real? A document can pass the first check completely and still be broken in ways that fail real tooling downstream.
Layer one: structural (schema) validation
OpenAPI documents are themselves described by a schema — the OpenAPI Specification defines exactly what a valid document looks like: an openapi (3.x) or swagger (2.0) version field, an info object with title and version, a paths object, and so on, each field with a defined type. A structural validator checks a document against this shape the same way JSON Schema Validator checks any JSON document against any schema — because that's literally what's happening under the hood. This layer catches typos in field names, wrong types (a string where an array is expected), and missing required fields.
Layer two: semantic checks a schema alone can't catch
A lot of real breakage passes structural validation cleanly. These rules require actually understanding what the fields mean, not just their shape:
- Duplicate
operationId. Every operation's ID is supposed to be unique across the whole document — code generators use it to name functions, and a collision means one of them silently gets clobbered or the generator errors out. - Undeclared path parameters. A path template like
/users/{userId}needs a matching{ in: "path", name: "userId", required: true }parameter declared — nothing in the schema shape enforces that the template placeholder and the parameter declaration actually agree with each other. - Broken
$ref. A schema only checks that$refis a string — not that the string actually points to something that exists in the document. A typo'd ref (#/components/schemas/Usre) is structurally perfect and semantically useless. - Responses missing a
description. The field is required by the spec on every response object, easy to forget when copy-pasting an example that omitted it.
Our own OpenAPI Validator checks exactly this list — version and info fields, duplicate operationIds, path parameters declared against the path template, required response descriptions, and local $ref resolution (an external or remote $ref gets flagged as unable to check, rather than silently assumed valid). For the full catalog of what breaks and why, see Common OpenAPI Validation Errors.
Real tooling: Redocly CLI
For validating in a terminal or CI pipeline, Redocly CLI is the current standard:
npx @redocly/cli@latest lint openapi.yamlIt runs both layers at once — structural validation plus a configurable ruleset of semantic and style rules — and reports problems with the exact location in the file. If you find older tutorials pointing at swagger-cli instead: that package is deprecated, and Redocly CLI is its official replacement, covering the same validation plus considerably more.
For enforcing your own organization's standards beyond what a generic validator checks — requiring every operation to have a specific tag, banning a deprecated field, enforcing a naming convention — Spectral is the tool built for custom rulesets, and it's what several hosted API-design tools use under the hood.
The 3.0-vs-3.1 gotcha
Validating against the wrong OpenAPI version's rules produces confusing false results, and the schema-related fields changed enough between 3.0 and 3.1 that this happens often:
- Nullable fields. OpenAPI 3.0 uses its own custom keyword —
{ type: "string", nullable: true }. OpenAPI 3.1 dropped that in favor of plain JSON Schema union types —{ type: ["string", "null"] }. A 3.0 document validated with 3.1 rules (or vice versa) fails on this immediately. exclusiveMinimum/exclusiveMaximum. In 3.0 these are booleans that modifyminimum/maximum. In 3.1 they're numbers on their own, matching standard JSON Schema 2020-12 — the draft OpenAPI 3.1 fully aligns with.
If a validator reports an error on a field that looks completely ordinary, check which draft it's validating against before assuming the document is actually broken.
Wiring it into CI
A practical setup treats the two severities differently: fail the build on anything that breaks real tooling — broken refs, duplicate operation IDs, missing required fields — and surface anything else (style suggestions, missing but non-required descriptions) as a warning that doesn't block a merge. Redocly CLI's exit code reflects error-level findings by default, so a plain redocly lint step in a pipeline already gives you that split without extra scripting.
Common mistakes specific to this case
- Stopping at schema validation. A document with zero schema errors can still have duplicate operation IDs or a dangling
$ref— the two layers catch different things, and neither substitutes for the other. - Validating against the wrong version's rules. See the 3.0-vs-3.1 section above — a version mismatch produces errors that have nothing to do with an actual mistake in the document.
- Assuming YAML indentation errors show up as OpenAPI errors. A YAML parser failure happens before OpenAPI-specific validation ever runs, and the resulting message describes a YAML syntax problem, not a spec violation — worth knowing so you don't go hunting through the OpenAPI spec for a rule that was never the issue.
- Ignoring warnings on an external or unbundled
$ref. A ref pointing at another file or a URL usually can't be checked without first bundling the document into one file — a validator flagging it as "can't verify" isn't the same as it being confirmed valid.
Try it yourself
Paste a document into OpenAPI Validator for an instant check of both layers — schema shape plus the semantic rules above. To read the document as endpoint documentation rather than raw YAML/JSON, use OpenAPI Viewer. Both run entirely in your browser; nothing you paste is uploaded.