The OpenAPI Guide: Spec, 3.2, Design, and How It Works
Quick answer: OpenAPI is a machine-readable contract that describes an HTTP API — its paths, operations, request and response shapes, authentication, and examples. Treat it as the single source of truth and let it drive design, debugging, testing, mocking, documentation, code generation, and MCP servers. The spec pays off when it lives in version control, gets validated in CI, and is written before the code; a document that is reverse-engineered after release and then left to drift is little more than decoration.
This guide collects the decisions that actually come up when you run OpenAPI on a real service: which version to target, how to split a document that grows past a few hundred lines, how to model the data shapes that break naive codegen, and how to keep the contract honest over time. Each section links to a focused article with concrete examples.
What OpenAPI is and when to write it
An OpenAPI document is a structured description of an API, usually written in YAML or JSON. The workflow that holds up over years is design-first: agree on the contract, review it like code, and only then implement against it. The case for doing this — and the cost of skipping it — is laid out in designing an API before the first line of code. If you are on the other side of that problem, inheriting a running service with no contract, the honest path is to scan code into OpenAPI without hallucinating; the story of doing exactly that is in we inherited a service with no OpenAPI.
Versions: 3.0, 3.1, and what 3.2 changes
Most teams should target 3.1 today, because it aligns the schema dialect with JSON Schema and removes the awkward nullable workaround. If you are deciding what to adopt, read what changed in OpenAPI 3.2 for SSE and AI. The practical differences between nullable, optional fields, and an explicitly omitted value are easy to get wrong; nullable, optional, and omitted data in 3.1 works through them with before/after documents.
Structure: multi-file documents and reusable components
Once a document covers more than a handful of operations, a single flat file becomes unmaintainable. Split paths and schemas into separate files and link them with $ref. The patterns for doing this without creating a tangle of relative paths are in reusable JSON Schema components and $ref. Two structures repeatedly cause trouble: recursive and cyclic schema refs (think trees and threads) and free-form maps, covered in additionalProperties and free-form objects.
Data modeling: the shapes that break naive clients
Plain objects and strings are easy; the edge cases are where generated clients and mocks fall apart. Work through these as you meet them:
- Polymorphism and inheritance — oneOf, anyOf, allOf, and discriminator
- Closed lists vs open text — enum, const, and open-ended values
- Numbers that overflow doubles — int64, bigint, and decimal money
- Timestamps, time zones, and durations — date-time, timezone, and duration formats
- Fields that appear only on input or output — readOnly and writeOnly fields
- Format keywords and how they flow into codegen and mocks — JSON Schema formats, constraints, codegen, and mocks
Operations: naming, servers, and common endpoint patterns
The metadata around an operation is what makes codegen and docs usable. Pick stable operationId values and consistent tag names following operationId and tag naming conventions, and describe environments with servers, server variables, and multiple environments. A handful of endpoint patterns recur across almost every API; each has a dedicated article:
- Search, filter, sort, and sparse fields
- Bulk and batch endpoints with 207 Multi-Status
- Long-running async jobs with 202 and Location
- File upload with multipart/form-data
- File downloads: CSV, PDF, and non-JSON responses
- PATCH: JSON Merge Patch vs RFC 6902 vs PUT
- Callbacks vs webhooks and runtime expressions
Authentication and security
Describe how a caller authenticates rather than leaving it to prose. Start with documenting API authentication with security schemes, then handle multiple schemes, AND/OR logic, and optional auth. Enforce the contract at the edge with request validation middleware, and expose the standard probes in health, readiness, and liveness endpoints.
Reliability: rate limits, concurrency, and deprecation
A good contract tells callers how to behave under contention and how to migrate. Communicate throttling with 429 and Retry-After, guard concurrent edits with ETag and If-Match, and retire endpoints cleanly using an API deprecation and Sunset policy.
Keeping the contract honest: linting, examples, and docs
A contract that silently drifts is worse than none, because people trust it. Enforce style and structural rules in CI with Spectral and a style guide, keep examples as a single source of truth — the failure mode when they don't is in a saved API example drifting from the spec — and render it for humans by choosing among Swagger UI, Redoc, Scalar, and Elements.
Recovering a spec from existing code
When the code came first, generate a draft from the framework rather than hand-writing it, then verify the result. The routes are framework-specific: generate OpenAPI from Express and NestJS, and from FastAPI and Django REST, with the general approach in generating OpenAPI from existing code. Understand where automated recovery must stay honest — and where it cannot infer intent — in AST vs AI reverse engineering and the honest gaps.
Where to go next
OpenAPI is the spine, but the contract only delivers value when it drives the rest of the lifecycle. From here, move into API testing, mocking, code generation, and — once you want AI agents to call the API correctly — the MCP server guide. If you are comparing tools to work with the spec, the Postman alternatives page compares the local-first, OpenAPI-first options.
FAQ
Should I write OpenAPI in YAML or JSON? YAML is more readable for hand-authored design and supports comments; JSON is easier to generate and diff mechanically. Both are valid; use whichever your workflow produces, and keep it in version control.
Can OpenAPI describe event-driven or streaming APIs? It describes the HTTP entry points well, and 3.2 adds better support for streamed responses; the semantics of brokers and message schemas are better expressed with AsyncJSON Schema or an event-specific spec. For testing streams, see SSE and WebSocket.
How do I stop generated clients drifting from the spec? Regenerate as part of CI, never hand-edit the output, and fail the build on diffs; the failure mode is documented in generated types silently drifting.