S SCHEMA.BIZ

Last updated:

OpenAPI Spec Editor

Write and validate OpenAPI 3.x specifications with a live documentation preview. Switch between YAML and JSON, use starter templates, and export your spec when ready.

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 validation errors appear inline against the lines that produced them.

Everything happens locally. The editor parses the spec with the same schema validators (Ajv against the official OpenAPI JSON Schema) that production CI pipelines use, but it does so against the bytes in your browser tab, not against a hosted service. That means 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. 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 validation panel catches subtler issues like unused components or a renamed operationId that breaks generated SDKs.

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 a "Page through the catalog" summary, two query parameters with their schema constraints visible (cursor as an opaque string, limit bounded between 1 and 100 with a default of 25), and two documented responses: a 200 that resolves to the inline ProductPage schema, and a 400 that resolves through the reusable BadRequest response component. Click into ProductPage and the editor expands the inline tree, showing that items is an array of Product references and next_cursor is a nullable string using the OpenAPI 3.1 array-type form.

POST /products renders next, with the request body marked required and resolved to ProductInput. ProductInput reuses the SKU pattern from Product through a deep $ref into #/components/schemas/Product/properties/sku — the editor follows the pointer, shows the regex inline, and the validator confirms the pointer resolves. The two responses (201 with the created Product, 409 for SKU collisions) appear with their descriptions; the absence of a 400 response on the POST gets surfaced in the warnings panel as "operation createProduct lacks an explicit 4xx error response," which is the kind of nudge that catches under-documented error contracts before they ship.

Switch the format toggle from YAML to JSON. The same spec re-emits as a JSON document with the structure preserved exactly — comments are dropped (JSON has no syntax for them), but the operation order, response order, and schema definitions are stable so the diff against your committed spec stays minimal.

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 lets you toggle between the two losslessly, so write in whichever you prefer and export the other when you need it.

Does the preview render exactly like Swagger UI or Redoc?

Visually it is closer to Redoc — endpoints grouped by tag, request and response side-by-side, schema expansion inline. The semantics match Swagger UI: operations are addressable by operationId, security requirements show up in the operation header, and content-type negotiation surfaces as tabs under each request and response.

Can I paste a spec that contains external $ref pointers?

External pointers like $ref: 'shared/types.yaml#/Product' will be flagged but not resolved, since the editor has no filesystem access. Inline the referenced fragments under components/schemas before pasting, or use the bundled spec your build pipeline produces. Internal pointers (#/components/...) work normally.

Does the editor warn me about unused components or undocumented operations?

Yes. The validator panel surfaces unused entries in components/schemas and components/responses, operations missing operationId, parameters declared with no schema, and 2xx responses without a content body. These are warnings rather than errors so you can keep working, but they tend to be the rough edges that bite consumers later.

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 toggle in the editor switches dialects and re-runs validation against the chosen draft.

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.