All posts

OpenAPI 3.x vs Swagger 2.0: What Changed and How to Validate a Spec

July 26, 2026 · DevTools

openapi
swagger
api
documentation
developer-tools

You inherit an API. The documentation is a wiki page from 2018 that links to a swagger.json file in the repo. You open the file, recognise every field, and try to generate a client SDK with the modern tooling — which promptly errors out because the file is Swagger 2.0 and the generator only accepts OpenAPI 3.x. The question you ask first is the right one: what actually changed?

You can try every example in this guide with the OpenAPI / Swagger Editor. Paste a spec — JSON or YAML, 2.0 or 3.x — and the tool parses it, checks that the required blocks are present, lists every operation, and re-emits a formatted JSON copy. Parsing is entirely client-side; nothing leaves your browser.

A quick bit of history

The format's history explains why both names still appear:

  • Swagger 1.x / 2.0 — created by SmartBear in 2011 and donated to the OpenAPI Initiative in 2015
  • OpenAPI 3.0 — the first release under the Linux Foundation stewardship, in 2017
  • OpenAPI 3.1 — released in 2021, harmonised with JSON Schema 2020-12
  • OpenAPI 3.2 — released in 2024, adds webhooks, streaming, and a few other long-requested features

In everyday speech, "Swagger" is still common even when the format is OpenAPI. The editor tools ("Swagger Editor", "Swagger UI", "Swagger Codegen") keep the original name; the file format is now OpenAPI. The two terms are mostly interchangeable today, except when you're talking about Swagger 2.0 specifically, which is the last format with the "Swagger" name.

The shape: what stayed the same

Both formats describe the same thing — an HTTP API — in roughly the same shape:

openapi: 3.1.0          # Swagger 2.0 says "swagger: 2.0"
info:                    # title + version + description (identical structure)
  title: Pet Store
  version: 1.0.0
servers:                 # Swagger 2.0 uses "host", "basePath", "schemes" instead
  - url: https://api.example.com/v1
paths:                   # same shape: /pets, /pets/{id}, etc.
  /pets:
    get:                 # operation: method + summary + parameters + responses
      summary: List pets
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
components:              # Swagger 2.0 uses "definitions" and "parameters" at the top level
  schemas:
    Pet:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }

If you've written one, you can read the other. The differences are concentrated in a few specific places.

What actually changed

1. The version field

Swagger 2.0OpenAPI 3.0OpenAPI 3.1
swagger: "2.0"openapi: 3.0.0openapi: 3.1.0

The version string is the simplest discriminator. If your file starts with swagger: "2.0", it's Swagger. If it starts with openapi:, it's OpenAPI 3.x.

2. Servers vs host + basePath + schemes

Swagger 2.0 had three separate fields — host, basePath, schemes — that combined to describe where the API lived:

# Swagger 2.0
swagger: "2.0"
host: api.example.com
basePath: /v1
schemes:
  - https

OpenAPI 3.x replaces all three with a single servers array, and adds templated variables:

# OpenAPI 3.x
openapi: 3.0.0
servers:
  - url: https://api.example.com/v1
  - url: https://{environment}.example.com/v1
    variables:
      environment:
        default: api
        enum: [api, staging]

If you're converting a 2.0 spec to 3.x, merge host, basePath, and schemes into one URL string and put it in servers.

3. definitionscomponents/schemas

Swagger 2.0 had definitions as a top-level key for reusable schemas. OpenAPI 3.x moves them under components/schemas:

# Swagger 2.0
definitions:
  Pet:
    type: object

# OpenAPI 3.x
components:
  schemas:
    Pet:
      type: object

The same applies to parameters and responses: both moved under components/.

4. Request bodies: separate from parameters

Swagger 2.0 used a single parameters array, with in: body distinguishing body fields from query and header fields. OpenAPI 3.x splits them:

# Swagger 2.0 — body mixed with parameters
paths:
  /pets:
    post:
      parameters:
        - name: pet
          in: body
          schema:
            $ref: "#/definitions/Pet"

# OpenAPI 3.x — requestBody is separate
paths:
  /pets:
    post:
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"

This is the single biggest source of converter errors. A Swagger-to-OpenAPI tool has to scan every parameters array and lift anything with in: body into a requestBody.

5. Form parameters split

Form parameters (in: formData) became first-class requestBody content with media types — application/x-www-form-urlencoded and multipart/form-data:

# OpenAPI 3.x
requestBody:
  content:
    multipart/form-data:
      schema:
        type: object
        properties:
          file:
            type: string
            format: binary

6. JSON Schema alignment

Swagger 2.0 used an older JSON Schema draft. OpenAPI 3.0 switched to a near-draft-05 superset; OpenAPI 3.1 is fully aligned with JSON Schema 2020-12. If you have validators or codegen that target a specific JSON Schema dialect, the same $ref and type definitions may validate differently across 3.0 and 3.1.

7. Webhooks (3.1)

OpenAPI 3.1 added a top-level webhooks key:

webhooks:
  newOrder:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Order"
      responses:
        "200": { description: OK }

Useful for describing GitHub-style webhook APIs without inventing your own structure.

A minimal spec, both versions

The same "list pets" endpoint in both formats, for comparison:

Swagger 2.0:

swagger: "2.0"
info:
  title: Pet Store
  version: 1.0.0
host: api.example.com
basePath: /v1
schemes: [https]
paths:
  /pets:
    get:
      summary: List pets
      responses:
        "200":
          description: OK
          schema:
            type: array
            items:
              $ref: "#/definitions/Pet"
definitions:
  Pet:
    type: object
    required: [id, name]
    properties:
      id:   { type: integer }
      name: { type: string }

OpenAPI 3.1:

openapi: 3.1.0
info:
  title: Pet Store
  version: 1.0.0
servers:
  - url: https://api.example.com/v1
paths:
  /pets:
    get:
      summary: List pets
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Pet"
components:
  schemas:
    Pet:
      type: object
      required: [id, name]
      properties:
        id:   { type: integer }
        name: { type: string }

The shape is recognisable in both — same info, same paths, same responses. The differences are mechanical: swagger/openapi at the top, servers instead of host+basePath+schemes, components/schemas instead of definitions, and requestBody separate from parameters.

Validating a spec before you ship

A spec that parses is not a spec that's correct. A few checks that catch the embarrassing bugs before the docs go live:

  1. Top-level version field present. swagger: "2.0" or openapi: 3.x.y — without it, every toolchain rejects the file
  2. info.title and info.version set. Most renderers use these as the page heading and the version badge; missing fields produce blank docs
  3. paths is non-empty. An empty paths: block describes no API; it's almost always a forgotten copy-paste
  4. Every operation has a responses block. Even a bare default: { description: ... } is enough
  5. Every $ref resolves. A broken $ref is the most common spec defect; importers fail on it silently
  6. No orphaned schemas. Components declared in components/schemas but never referenced via $ref — usually OK, but worth pruning
  7. No example used where examples is required — in OpenAPI 3.x, the plural is an array; the singular is a single value. Codegen treats them very differently

The OpenAPI / Swagger Editor checks the structural ones — version field present, info block complete, paths non-empty — and surfaces the rest as a browsable list so you can audit them by eye.

For a deeper validation, the canonical tool is swagger-cli (swagger-cli validate openapi.yaml), which checks $ref resolution and a lot of the spec's internal consistency rules. Run it in CI on every PR.

Endpoint browsing

A spec is also a navigation aid. Once parsed, the operations table tells the whole story of an API:

  • GET /pets — list pets
  • POST /pets — create a pet
  • GET /pets/{id} — fetch a single pet
  • PUT /pets/{id} — update a pet
  • DELETE /pets/{id} — remove a pet

For each operation, the useful fields are:

  • summary — one-line description, used in tooltips
  • description — longer Markdown description, used in the docs page
  • operationId — a stable identifier for codegen and routing
  • tags — group operations into sections in the docs UI
  • parameters — the inputs (path, query, header, cookie)
  • requestBody — the body payload (OpenAPI 3.x)
  • responses — every status code, with examples and schemas

The OpenAPI / Swagger Editor lists every operation with method, path, and summary, so you can browse a spec without spinning up Swagger UI.

JSON or YAML?

Both formats are equally valid for both versions. Pick whichever your team prefers and be consistent:

  • YAML is more compact and easier to read; the official OpenAPI examples use YAML
  • JSON is easier to generate programmatically and round-trips without quoting issues

If you're writing the spec by hand, YAML is more pleasant. If you're generating it from code or diffing it across many tools, JSON has fewer surprises. The editor accepts both and re-emits as JSON for a clean canonical view.

Common mistakes

  • Mixing 2.0 and 3.x fields. A definitions block in an OpenAPI 3.x file is silently ignored — no error, no warning, just a schemas section that's missing from the docs
  • type: file for uploads — valid in Swagger 2.0, removed in OpenAPI 3.x. Use type: string with format: binary inside multipart/form-data
  • Forgetting requestBody content type — every requestBody needs at least one entry under content: (e.g. application/json); the renderer needs to know what media type to advertise
  • Treating 3.0 and 3.1 as interchangeable — 3.1's full JSON Schema alignment breaks some 3.0 codegen that depends on the old behaviour. If you don't need 3.1 features, stay on 3.0.3
  • Missing operationId — codegen uses it for method names in client SDKs; auto-generated IDs collide across files

Everything stays in your browser

The OpenAPI / Swagger Editor parses with the standard js-yaml and JSON.parse libraries — locally, with no upload. That matters when the spec describes an internal API you don't want to leak, or when you want to inspect a third-party spec without pinging their servers.

Next steps