OpenAPI Validator & Viewer
Lint a Swagger 2.0 or OpenAPI 3.x spec against spec-cited rules and browse it as docs — findings jump to the exact line, entirely on this device.
Full rule packs for Swagger 2.0 and OpenAPI 3.0/3.1; OpenAPI 3.2 runs the shared core — 3.2-only members are recognized, not deep-checked.
Paste a spec and validate to see findings and a docs view.
Each finding cites the spec clause it comes from and jumps to the offending line.
Nothing you paste here is uploaded — parsing, validation and the docs view run in a Web Worker in this tab. There is no try-it-out console — a live API call would leave this device, so it is out of scope by design.
Press ? for shortcuts
Do it offline:$ npx @redocly/cli lint openapi.yaml
How to use it
- Paste your spec, or drop a .yaml, .yml or .json file — a dropped file is read on this device and never uploaded.
- Leave the ruleset on auto-detect, or force one when a document is missing its version member, then select Validate.
- Work through the Problems tab: each finding shows its severity as a word, the message, the rule id with its spec citation, and a line:column button that jumps your cursor to the offending spot.
- Switch to the Docs tab to browse the same spec as documentation — operations with their response codes, named schemas, and any references the page refused to fetch.
- Copy the report, download it as a .txt file, or share a link that carries your view settings — never your spec.
Paste an OpenAPI 3.x or Swagger 2.0 description — YAML or JSON — and this page lints it against a curated pack of spec-cited rules, then projects it as browsable docs. Every finding names its rule, cites the clause it comes from, and jumps to the offending line. Parsing runs in a Web Worker on your device; remote $refs are never fetched, and there is no try-it-out console to call your API.
How it works
Parsing is format-aware. A document whose first significant character is a brace or bracket goes through a strict JSON pass that keeps an exact position for every member; anything else is composed as YAML with the parser's own node ranges retained. Both routes end at the same place: a resolved document plus an index from every member to its source line, so findings are line-anchored whichever serialization you paste. The whole pipeline runs inside a Web Worker sized for specs up to 5 MB.
Every location in the document gets an RFC 6901 JSON Pointer — the #/paths/~1pets/get spelling, where ~1 stands for a slash inside a segment. A finding carries that pointer, the decoded path it spells, and the line and column behind it. For YAML input the printed path matters as much as the line, because an alias-expanded member has no literal line of its own; the anchor then falls back to the nearest written ancestor.
The rule pack is hand-curated, not a meta-schema run: forty-two rows covering required members per version, path templating (declared against used variables, duplicated template names, colliding normalized paths), operationId uniqueness, reference-target existence, response-map shape, security-scheme wiring, Swagger 2.0 body and formData parameter law, and schema divergences between the generations. Each row is written against a named clause and every finding prints that citation, so a report is checkable against the specification rather than against this tool's opinion. Severity is a fixed property of the rule, and past 500 findings the list is cut with an honest note rather than silently.
The Docs tab is a projection of the validated document, not a second product: operations are walked out of paths (and, from 3.1, webhooks) with their parameters, request bodies and response codes; named schemas come from components or definitions with their types and property counts. A reference that points at another file or a remote URL renders as an unresolved leaf, and nothing on the page can issue a request — the viewer is documentation, never a console.
Version detection reads the document itself: a swagger member equal to the string 2.0 selects the Swagger pack, while an openapi member starting 3.0, 3.1 or 3.2 selects that generation — anything else is a typed refusal, not a guess. Swagger 2.0, OpenAPI 3.0 and 3.1 get their full packs; OpenAPI 3.2 runs the shared core, with 3.2-only members recognized rather than deep-checked.
Examples
openapi: 3.0.4
info:
title: Duplicate ids
version: "1.0.0"
paths:
/pets:
get:
operationId: listPets
responses:
"200": {description: OK}
/owners:
get:
operationId: listPets
responses:
"200": {description: OK}operationId "listPets" is already used at #/paths/~1pets/get — operationIds must be unique across the document.Two operations share one operationId, so the second occurrence — line 13 — is the offender. The finding points at the earlier definition by pointer, and OAPI-R013 cites the Operation Object clause that makes uniqueness a hard requirement.
openapi: 3.1.1
info:
title: Broken ref
version: "1.0.0"
paths:
/pets:
get:
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"$ref points at #/components/schemas/Pet, but nothing exists at that pointer.The response schema references a component that was never declared. OAPI-R017 resolves the pointer locally, finds nothing, and anchors the error to line 14 where the $ref is written.
openapi: 3.1.1
info:
title: Type arrays are 3.1 law
version: "1.0.0"
paths:
/pets:
get:
operationId: listPets
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
components:
schemas:
Pet:
type: object
properties:
tag:
type: [string, "null"]No findings — every rule in this pack passed.Under the 3.1 pack a type array such as string beside null is the correct spelling of a nullable property, so this document comes back clean. Validate the identical schema as 3.0 and the type array becomes an error instead — the packs genuinely differ.
openapi: 3.1.1
info:
title: Migration
version: "1.0.0"
paths: {}
components:
schemas:
Pet:
type: object
properties:
tag:
type: string
nullable: trueThe schema "Pet" is declared but never referenced by any $ref in this document.
nullable: true has no effect in OpenAPI 3.1+ — use a type array instead, e.g. type: [string, "null"].A report can carry several findings, sorted by line. Here the unreferenced schema is a warning, and OAPI-R024 flags the 3.0-era nullable keyword — silently inert in 3.1 — with the type-array spelling to migrate to.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
OpenAPI 3.2 runs the shared core
Swagger 2.0, OpenAPI 3.0 and 3.1 get their full rule packs. A 3.2 document is detected and linted with the rules the generations share, and 3.2-only members — $self at the root, the query HTTP method — are recognized rather than deep-checked. Depth for 3.2 is a follow-up, and the page never pretends otherwise.
Remote $refs are findings, not fetches
A reference whose target starts with http or https raises OAPI-R018 and appears in the docs tree as an unresolved leaf. There is no network path in this tool, so a spec split across URLs validates file by file — paste each part on its own.
YAML anchors meet an alias budget
Anchors and aliases are expanded during composition, and merge keys resolve the way the YAML parser defines. Expansion is held to a 400-alias budget, so a billion-laughs document is refused with a typed error instead of freezing the tab.
webhooks is a 3.1 word
A webhooks map at the root of a 3.1 or 3.2 document is walked like paths, and its operations join the docs tree. The same member in a 3.0 document raises the unknown-root-member warning with a note saying which generation introduced it.
jsonSchemaDialect is checked for shape only
From 3.1 a document may name the JSON Schema dialect its schemas are written in. This page checks that the member is a non-empty URI string and stops there — it does not switch schema semantics by dialect, which is exactly the kind of depth the scope note above declares out of bounds.
Duplicate YAML keys keep the later value
When a mapping repeats a key, composition follows the parser's posture: the later value wins and a duplicate-key warning chip names the line. Unquoted status codes like 200 compose as numbers and are coerced to the string keys the responses map needs, so they pass the key-shape rule unchanged.
Common errors
Neither an openapi nor a swagger version member exists at the document root — this tool needs one to pick its rule pack (or force a ruleset).- Version detection reads the document, not the file name. Add openapi: 3.1.1 (or swagger: "2.0") at the root, or force a ruleset from the Rules select to validate a fragment.
Tabs are not allowed as indentation at line 2, column 1 — YAML forbids tabs as indentation; use spaces.- YAML's grammar reserves indentation for spaces. An editor that inserts tabs breaks composition at the first tabbed line — the position points at the exact character to replace.
The document root is an array — an OpenAPI description is a single object with a version member.- A spec is one object, never a list. This usually means a fragment was pasted — a bare paths list, an array of operations — rather than the whole document.
The openapi member must be a string, but this document carries the number 3 — YAML reads an unquoted 3.0 as a number; quote the version, e.g. "3.0.4".- In YAML an unquoted 3.0 is the float three, and floats drop their trailing zero — so the version member arrives as a number the spec does not allow. Quoting the value fixes detection.
FAQ
Does my spec get uploaded when I validate it?
No. Parsing, the rule pack and the docs projection all run in a Web Worker inside this tab. The share button is safe for the same reason: a shared link carries your view and ruleset settings only — the spec text never enters the URL.
Can this page call the API my spec describes?
No. There is deliberately no try-it-out console: executing a request against your servers is a live network call, which this page rules out by design. The Docs tab shows what the API declares; exercising it belongs to a client with your credentials.
Is Swagger 2.0 really supported, or just renamed OpenAPI?
It has its own pack. Swagger 2.0 differs structurally — definitions instead of components, host and basePath instead of servers, and typed non-body parameters with at most one in:body payload — and those rules only run when the swagger member selects the 2.0 ruleset.
How deep does OpenAPI 3.2 support go?
A 3.2 document is detected from its version member and linted with the rules shared across generations. Members that exist only in 3.2 — $self, the query method — are recognized so they never raise false unknown-member warnings, but no 3.2-exclusive deep rules ship yet. The scope line above the editor states this matrix exactly.
Why does a remote $ref show up as a warning instead of resolving?
Because resolving it would be a network request, and this page makes none. Remote and file references are typed findings with their own rule ids, and the docs tree lists them as unresolved leaves so nothing pretends to have read them.
Should I paste YAML or JSON?
Whichever you have. The same document produces the same findings at the same JSON Pointers in both serializations; only the line numbers differ, because the two formats lay the members out differently. Line anchoring works in both — JSON through a position-keeping parse, YAML through the parser's node ranges.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Validate the spec — the explicit run this size class requires.
- ⌘/Ctrl + S
- Save the findings report as a .txt download.
- ⌘/Ctrl + C
- Copy the full findings report while nothing is selected.
- ⌘/Ctrl + Shift + C
- Copy the findings even mid-selection in the spec editor.
- t
- Cycle the site theme.
- ? or ⌘/Ctrl + /
- Show every binding on this page in a dialog.
- Esc
- Close the help dialog without touching your spec.