OpenAPI is a format for describing an HTTP API in a plain YAML or JSON file. The file lists the paths the API exposes, the methods they accept, the fields that go in a request and what comes back in a response. Both people and programs can read it, which is why documentation, client libraries and test mocks can all be generated from the same source.

Its formal name is the OpenAPI Specification, or OAS. It is maintained by the OpenAPI Initiative, an open governance project under the Linux Foundation.

What follows is how the document is put together, how OpenAPI differs from Swagger, what changed in 3.1 and 3.2, and why the format's central promise — documentation that never goes stale — does not hold on its own.

What an OpenAPI document looks like

A working file is shorter than most people expect:

openapi: 3.1.0

info:
  title: Links
  version: "1.0"

paths:
  /links:
    post:
      summary: Create a short link
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url:
                  type: string
                  format: uri
      responses:
        '201':
          description: Link created

Only two top-level fields are required, openapi and info, and the document must carry at least one of paths, components or webhooks. That is how the OAS 3.1.1 specification defines it.

One trap sits right there in those first lines: openapi and info.version are different things. The first is the version of the format itself, which tooling reads to decide how to interpret the document. The second is the version of your API. The spec says so explicitly, but intuition suggests otherwise, which is how files declaring openapi: 1.0 come to exist.

Larger specifications are usually split across files with $ref — the root document points at ./paths/links.yaml, which points at ./schemas/link.yaml. Convenient, and the source of a problem I will come back to at the end.

OpenAPI and Swagger are not the same thing

This is the most common confusion in the subject.

What it is Who maintains it
OpenAPI the standard, the format itself OpenAPI Initiative, Linux Foundation
Swagger a family of tools that read that format SmartBear

The history explains the muddle. Swagger originally meant both the specification and the tooling. The specification was then donated to the OpenAPI Initiative and became the OpenAPI Specification, while the Swagger name stayed with the tools — Swagger UI, Swagger Editor and the rest.

In practice, "we have Swagger" almost always means "we have an OpenAPI file and we render it with Swagger UI". Nothing ties the two together: the same file opens in Redoc, Scalar or a client generator just as well.

Versions: 3.0, 3.1 and 3.2

The differences between branches are not cosmetic.

3.0 is still the most widely deployed. Its weak point is data schemas: they resemble JSON Schema without being fully compatible with it, which keeps breaking interoperability between tools.

3.1 closed that gap. The Schema Object became a superset of JSON Schema Draft 2020-12, so schemas you already maintain as JSON Schema can be reused instead of rewritten. The current revision, 3.1.1, was published on 24 October 2024.

3.2 arrived in September 2025. From the OpenAPI Initiative announcement, the notable additions are:

  • the query HTTP method, for idempotent reads whose parameters do not fit in a URL;
  • additionalOperations for non-standard methods;
  • streaming media types: Server-Sent Events, JSON Lines, JSON sequences;
  • hierarchical tags, via summary, parent and kind;
  • OAuth 2.0 device flow for devices with awkward input.

Pick the version your tooling actually supports rather than the highest number. Ecosystem support for 3.1 is solid; 3.2 is newer and parts of the toolchain are still catching up. Our own specification declares 3.1.0.

What the specification buys you

One file covers several jobs at once:

  • interactive documentation — Swagger UI or Redoc build the page straight from the file, including a "try it" form;
  • client libraries — generators produce an SDK for your language instead of hand-written wrappers;
  • mocks — a mock server can run off the description before the backend exists, so the frontend is not blocked;
  • contract tests — tests compare real responses against the schemas and catch the moment a response stops matching its description.

That list is where the main argument for OpenAPI comes from: documentation stops being a separate document that somebody forgets to update.

The argument is sound. It is just not self-executing.

Nothing connects the specification to the code

This is the part that "what is OpenAPI" articles rarely mention, and the part that bites in production.

A specification file is just a file. The router does not know about it. Neither does the controller. You can delete an endpoint, add one, or turn a required field optional, and not a single test will fail. Documentation does not synchronise itself; it stays accurate exactly as long as someone remembers to edit it.

Drift runs in both directions, and each direction hurts differently:

  • documented but not working. A developer reads the docs, sends the request, gets a 404. Trust in the rest of the documentation collapses immediately — if this was wrong, what else is?
  • working but not documented. The capability simply does not exist for anyone looking for it. Nobody finds it, nobody pays for it.

The second case is the more dangerous one, because nobody complains. There is nobody to complain: the user does not know the endpoint is there.

That is exactly what happened to us. The Lix.li API had three working conversion endpoints — an event feed plus single and batch postback ingestion — and none of them appeared in the specification. The code worked, the tests passed, and the API landing page meanwhile advertised two entirely different paths that did not exist in the code at all. It surfaced during a full audit, not from a bug report.

Catching drift automatically

The fix is a test, not discipline. The idea is plain: take the list of paths from the specification, take the list of routes from the router, and compare the two sets in both directions.

paths from openapi.yaml   →   does the route exist?   →   otherwise the docs lie
routes from the router    →   does the path exist?    →   otherwise the feature is invisible

The "documented things work" half is easiest to check with a live request: call the endpoint without credentials and assert the response is 401 rather than 404. The distinction matters. 401 means the route was found and needs a key; 404 means there is no such path. Nothing gets written along the way, because authentication runs before the controller does.

The reverse check reads the route configuration. It will also force you to list the exceptions explicitly — internal endpoints that have no business in a public contract. That list is valuable on its own: it makes you decide once, deliberately, what counts as your public API.

We wrote that test after the conversions incident. It earned its keep straight away: remove conversions from the specification again and the test fails, naming both missing paths.

Two traps you discover late

$ref and relative paths. When a specification is split across files, the references inside it resolve relative to the URL the file was served from. A root document available at /openapi will look for ./paths/links.yaml at /paths/links.yaml and find nothing. In a browser everything looks fine, because Swagger UI loads the file from its real location — but a client generator pointed at the same document will fail. Either serve the specification only from its own directory, or bundle it into a single file with no external $ref.

Swagger UI is empty to a crawler. A Swagger UI page is a few lines of markup plus a script that draws the content in the browser. The source HTML contains not one endpoint path. For a reader the page is fine; for indexing it is blank. Add to that a common robots.txt mistake: a Disallow: /api/ rule written to hide JSON endpoints also hides the human documentation living under the same prefix. Block the exact versioned path — /api/1.0/, say — rather than the whole section.

Where to start

If the API exists and the description does not, the order is roughly:

  1. Describe one endpoint by hand. That is enough to understand the structure.
  2. Choose the version your tooling supports confidently; 3.1 by default.
  3. Split the file with $ref once it outgrows a screen, and settle immediately which URL it is served from.
  4. Add a test that compares the specification against the router in both directions. Until that step, any claim about documentation staying current is a promise, not a property.

To see how this looks on a live API, the Lix.li API documentation covers links, groups, A/B tests and conversions. For the service built around that API there is the short link API page, and on neighbouring topics, what webhooks are, a breakdown of 301, 302, 307 and 308 redirects, and a worked example using the PHP SDK.