Last updated:
OpenAPI Spec Editor
Write OpenAPI 3.x specifications with basic checks and a live documentation preview. Switch between YAML and JSON, use starter templates, and export your spec when ready. Your spec is autosaved in this browser.
What this tool does
An OpenAPI document is the contract every consumer of your API silently depends on, but the tooling for writing one usually splits across three windows: an editor for the YAML, a separate Swagger UI tab for the preview, and a terminal running a linter. By the time you have your spec rendering correctly, you have already lost the train of thought you started with. This editor folds those three steps into a single tab. You type into the spec on the left, the rendered documentation updates on the right within a few hundred milliseconds, and a list under the editor shows the problems the basic checks find, with the path to each one.
Everything happens locally. The editor parses the spec in your browser tab and runs basic structural checks: the openapi version, info.title and info.version, path keys, and a responses object on every operation, plus suggestions for missing summaries, operationIds, and servers. It doesn't validate against the official OpenAPI JSON Schema yet, so keep a full validator in CI. You can sketch out specs for unreleased endpoints, paste in vendor APIs you are reverse-engineering, or work on a spec that includes internal hostnames, without any of it leaving the device: the editor autosaves the text in this browser's local storage, and We record anonymous usage metadata as you edit: the OpenAPI version, counts of paths, operations and schemas, the numbers of errors and warnings, and the kind of host your servers use (public, localhost, private or example), never the spec, its paths or its server addresses. When you are done you can export YAML or JSON, copy it directly into a Git repository, and feed it to OpenAPI Generator or Prism to produce code or a mock server.
When to use the OpenAPI Editor
Sketching a new endpoint before writing any code. The fastest way to negotiate an API shape with a client team is to write the spec first and let them comment on the schema, the parameter list, and the error contract. Two passes in the editor will save you a week of integration churn after the endpoint ships.
Writing a spec for an API you already built. Most internal services were not built spec-first. Reverse-engineering an OpenAPI document from existing handlers is faster when you can paste in a draft, see the preview render, and iterate on the missing pieces — request bodies, response examples, error codes — until the documentation matches what the server actually does.
Reviewing a spec change in a pull request. A diff in YAML is hard to mentally render into "what does the documentation now look like for the consumer?" Pasting the proposed full spec into the editor gives you the rendered preview to evaluate, and the basic checks catch structural slips such as an operation without responses.
Drafting cross-team API contracts. When two teams own different ends of an integration, a spec is the negotiation artifact. The editor's live preview means both sides can look at the rendered documentation in a screen share, point at fields, and agree on field names and types in real time without anyone needing to set up a local server.
Walkthrough with a real example
Paste this 3.1 spec for a small product catalog API:
openapi: 3.1.0
info:
title: Catalog API
version: 1.4.0
description: Read and write the product catalog.
servers:
- url: https://api.example.com/v1
paths:
/products:
get:
operationId: listProducts
summary: Page through the catalog
parameters:
- name: cursor
in: query
schema: { type: string }
- name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
responses:
'200':
description: A page of products
content:
application/json:
schema:
$ref: '#/components/schemas/ProductPage'
'400':
$ref: '#/components/responses/BadRequest'
post:
operationId: createProduct
summary: Add a new product to the catalog
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProductInput'
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
'409':
description: SKU already exists
components:
schemas:
Product:
type: object
required: [id, sku, name, price_cents]
properties:
id: { type: string, format: uuid }
sku: { type: string, pattern: '^[A-Z0-9-]{3,32}$' }
name: { type: string, minLength: 1, maxLength: 200 }
price_cents: { type: integer, minimum: 0 }
created_at: { type: string, format: date-time }
ProductInput:
type: object
required: [sku, name, price_cents]
properties:
sku: { $ref: '#/components/schemas/Product/properties/sku' }
name: { type: string, minLength: 1, maxLength: 200 }
price_cents: { type: integer, minimum: 0 }
ProductPage:
type: object
required: [items, next_cursor]
properties:
items:
type: array
items: { $ref: '#/components/schemas/Product' }
next_cursor:
type: [string, 'null']
responses:
BadRequest:
description: Invalid request
content:
application/json:
schema:
type: object
properties:
error: { type: string }
detail: { type: string }
On load, the right pane renders a documentation page with two operations. GET /products shows its "Page through the catalog" summary, the two query parameters with their types (cursor is a string, limit an integer), and its responses. The 200 expands one level into the ProductPage schema, where next_cursor shows as string | null, the OpenAPI 3.1 array-type form of a nullable string. The 400 comes from the reusable BadRequest response component, which the preview doesn't resolve yet, so it shows the status code alone; parameter bounds and defaults (limit between 1 and 100, default 25) aren't shown yet either.
POST /products renders next, with the request body marked required and resolved to ProductInput. ProductInput reuses the SKU field from Product through a deep $ref into #/components/schemas/Product/properties/sku, and the preview follows the pointer. The two responses (201 with the created Product, 409 for SKU collisions) appear with their descriptions. The basic checks find nothing to report, so the status line reads "Valid OpenAPI spec". They don't notice that the POST has no 400 response; under-documented error contracts are still a review item for you.
Switch the format toggle from YAML to JSON and the same spec re-emits as JSON. Check the result before you commit it: comments are dropped (JSON has no syntax for them), unquoted values such as version: 1.0 or a date can change type, and numeric response codes can be reordered. Quote version numbers and dates in YAML to keep them as strings.
OpenAPI concepts you should know
Paths, operations, and operationId. A path is a URL template like /products/{id}. Each path holds one operation per HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS). Each operation has an operationId that becomes the function name in generated SDKs. Operation IDs must be globally unique across the spec — duplicates produce code generators that overwrite functions silently.
Parameters and where they live. Parameters declare in: path, in: query, in: header, or in: cookie. Path parameters must have required: true (a path is meaningless without them); query parameters default to optional. The request body is not a parameter — it has its own top-level requestBody field with content keyed by media type.
Components and $ref. Anything reusable goes in components: schemas, responses, parameters, request bodies, headers, security schemes, examples, links, and callbacks. References use JSON Pointer syntax — $ref: '#/components/schemas/Product' resolves to the Product schema definition. References can be deep (point inside another schema) but every hop adds friction for code generators.
Responses keyed by status code. The responses object is keyed by HTTP status code as a string, with the special key default for the catch-all. Each response has a description (required), an optional content map keyed by media type, and optional headers. A response with no content body still requires the description.
Schema objects in 3.0 vs 3.1. OpenAPI 3.0 used a modified subset of JSON Schema with quirks like nullable: true, no const, and a hand-curated list of supported keywords. OpenAPI 3.1 dropped all of that and adopted JSON Schema 2020-12 wholesale, so you can use const, prefixItems, type arrays for nullability, and the same vocabulary your validators understand.
Security schemes and requirements. Define authentication mechanisms once under components.securitySchemes (apiKey, http, oauth2, openIdConnect, mutualTLS), then reference them under security at the document or operation level. An empty security array on an operation means "explicitly no auth," distinct from inheriting the document default.
Common mistakes
Duplicate or auto-generated operationIds. When operationId is missing, code generators invent one from the path and method, and two endpoints with similar paths can collide. Always set operationId explicitly with a verb-noun phrasing like listProducts or createProduct.
Putting query parameters inside the request body. Filtering and pagination belong in query parameters; the body is for the resource representation. Mixing them produces ergonomically broken SDKs where listing endpoints take an empty body and a filter object.
Documenting only the happy path. A spec with only a 200 response is half a contract. Consumers need to know the 4xx and 5xx shapes — what fields the error envelope contains, which codes mean "retry" vs "stop." A reusable responses.BadRequest component makes this easy to apply consistently.
Using example when you mean examples, or vice versa. They behave differently and live in different places:
# Wrong — 'examples' on a schema does not surface in most renderers
schema:
type: object
examples:
sample: { "id": "abc" }
# Right — 'example' (singular) on a schema, or 'examples' on a media type
content:
application/json:
schema:
type: object
example: { "id": "abc" }
examples:
ok:
summary: A typical response
value: { "id": "abc" } Putting required on the wrong level. The required array goes on the parent object schema, not on individual properties. Inside an application/json request body, the required on the requestBody itself controls whether the body must be present; the required on the schema controls which properties must be present once the body is provided. They are independent.
FAQ
Should I write my OpenAPI spec in YAML or JSON?
YAML for authoring, JSON for tooling exchange. YAML is denser, supports comments, and is easier to diff in pull requests; JSON is the lingua franca for code generators and HTTP-based importers. The editor converts between the two, but not losslessly yet: comments are dropped, and unquoted values such as version: 1.0 or a date can change type, so quote them in YAML. Write in whichever you prefer and export the other when you need it.
Does the preview render exactly like Swagger UI or Redoc?
Neither. It's a simpler preview: operations are grouped by tag (untagged ones under Other), and each expands to show its parameters, request body, and responses, with schemas expanded one level. It doesn't yet resolve $refs to shared responses or parameters, show parameter constraints, or render webhooks, so use Swagger UI or Redoc for the final rendering.
Can I paste a spec that contains external $ref pointers?
External pointers like $ref: 'shared/types.yaml#/Product' aren't resolved, since the editor has no filesystem access; the preview shows those schemas as unknown. Inline the referenced fragments under components/schemas before pasting, or use the bundled spec your build pipeline produces. Internal pointers to schemas (#/components/schemas/...) resolve in the preview; pointers to shared responses, parameters, and request bodies don't yet, and broken pointers aren't reported.
Does the editor warn me about unused components or undocumented operations?
Not yet. The checks cover the document's structure: a missing or malformed openapi version, a missing info.title or info.version, path keys that don't start with /, and operations without responses are errors; empty paths are a warning; and missing summaries, operationIds, descriptions, and servers are suggestions. Unused components, parameters without a schema, and 2xx responses without a body aren't flagged yet.
What changes between OpenAPI 3.0 and 3.1 that I will actually feel?
Three things: nullable: true is replaced by type: [string, "null"] as a JSON Schema array; the schema object is now full JSON Schema 2020-12, so const, prefixItems, and unevaluatedProperties work; and webhooks become a top-level concept alongside paths. The editor accepts both 3.0 and 3.1 documents and runs the same basic checks on each; its starter templates use 3.0.3.
Related tools and guides
- Breaking Change Detector — once you have two versions of a spec, paste them both in to get a categorized diff: which changes break clients, which are safe to release as a minor version, which deserve a warning.
- JSON Schema Validator — when an OpenAPI schema fails to round-trip with a real payload, lift the schema out of the spec and validate it against the actual JSON to find the gap.
- Schema Format Converter — turn the schemas in your components section into TypeScript interfaces, Zod validators, Pydantic models, or Go structs for the consumer side.
- API Versioning Guide — covers when to bump major versus minor versions, how to deprecate endpoints, and the migration patterns that keep clients from breaking.
- Complete Guide to JSON Schema — the JSON Schema vocabulary that powers OpenAPI 3.1 schema definitions, with patterns for composition, conditional validation, and reuse.