OpenAPI and API Contract Generation
An OpenAPI document is a machine-readable description of an HTTP API: its paths and operations, parameters, request and response bodies, error formats, and authentication. When the document is accurate, it becomes the engine for everything around the API — reference documentation, client SDKs and TypeScript types, server stubs, request validation, mock servers, generated tests, and automatic breaking-change detection. When it drifts from what the server actually does, every one of those artifacts becomes quietly wrong.
This guide is about treating the OpenAPI document as a contract: choosing a single source of truth, generating everything else from it, and wiring checks into CI so that the specification, the implementation, and the clients cannot silently disagree.
TL;DR
- Choose one source of truth — the spec file (design-first) or the code (code-first) — and never hand-edit the other.
- Generate documentation, clients, types, mocks, and validators from the contract instead of writing them separately.
- Lint the spec with rules for naming, errors, and pagination so the API stays consistent.
- Fail CI on breaking changes unless they are deliberate and versioned.
- Validate the implementation against the spec with schema-driven tests, so drift is caught before release.
- Commit the spec and review its diff in every pull request.
Quick Example
A code-first FastAPI service whose OpenAPI document is exported in CI, checked for drift and breaking changes, and used to generate TypeScript types for the web client:
Removing total_cents, or making a new request field required, now fails the build before any client breaks.
Core Concepts
What an OpenAPI Document Contains
Operations are identified by operationId, reusable pieces live under components, and $ref links them. OpenAPI 3.1 aligned its schemas with JSON Schema; a 3.2 release followed in 2025. Check that every tool in your pipeline supports the version you choose.
Design-First Versus Code-First
Both work. What fails is a hybrid in which people edit the spec by hand and the code independently, hoping they match.
What You Can Generate
Schemathesis applies property-based testing to APIs: it generates requests from the schema and checks that responses conform to it and that the server never returns a 500.
Preventing Drift
Code-First: Commit and Diff the Spec
Export the spec in CI, commit it, and fail the build when the exported spec differs from the committed one. Reviewers then see API changes as a readable diff of openapi.json, separate from the implementation details.
Design-First: Validate the Implementation
Run the server's tests with request and response validation middleware enabled, so any response that does not match the spec fails a test. Add a schema-driven fuzzing run against a test deployment, and use contract testing — bi-directional contracts compare consumer expectations with the provider's published spec.
Detect Breaking Changes
Tools such as oasdiff compare two versions of a spec and classify each change:
Renaming an operationId breaks nothing on the wire, but it renames methods in every generated SDK. Treat operation and schema names as part of the public API. Deliberate breaking changes belong in a new version — see API Versioning.
Linting for Consistency
A linter enforces house rules that the OpenAPI format itself does not: every operation has an operationId and description, paths are kebab-case, errors use the shared problem format, list endpoints use the same pagination parameters. Spectral rulesets are YAML:
Run the linter in CI on every change to the spec, and in editors for fast feedback. Shared rulesets make many teams' APIs feel like one product — the consistency goals in API Design.
Generated Clients in Practice
- Pin generator versions: a generator upgrade can change every generated file.
- Decide where generated code lives: commit it for easy review and builds without tooling, or generate it in CI for less noise — but be consistent.
- Wrap generated clients thinly where you need retries, authentication, or telemetry, rather than editing generated files.
- Publish SDKs with versions tied to the spec's version, and generate changelogs from spec diffs.
Best Practices
Name Things for the Code That Will Be Generated
operationId: getOrder becomes client.getOrder(); an inline schema becomes GetOrdersOrderIdGet200Response. Choose operation IDs deliberately and define shared schemas under components with good names.
Describe Errors and Security
Document every error response, ideally using the problem details format from RFC 9457, and declare security schemes and per-operation requirements. Generated clients and documentation are only as complete as the spec.
Add Examples
Examples make documentation useful, power mock servers, and serve as test fixtures. Validate them against their schemas so they do not rot.
Keep the Spec Next to the Code
Store the specification in the same repository as the service, review its changes with the code, and publish it at a stable URL such as /openapi.json for tools and partners.
Common Mistakes
Editing a Generated Spec by Hand
Default Operation IDs
Schemas So Loose They Say Nothing
A Published Spec Nobody Checks
FAQ
Should we use design-first or code-first?
Design-first suits public APIs, APIs consumed by several teams, and organizations that review API designs before building them. Code-first suits internal services where the team owning the code owns the API and wants zero duplication. Either works if exactly one side is the source of truth and the other is generated or validated.
What is the difference between OpenAPI and Swagger?
Swagger was the original name of the specification and remains the brand of a family of tools, such as Swagger UI and Swagger Editor. The specification was donated to the OpenAPI Initiative in 2015 and renamed the OpenAPI Specification; "Swagger 2.0" files are the predecessor of OpenAPI 3.
Which OpenAPI version should we use?
OpenAPI 3.1 is widely supported and aligns schemas with JSON Schema, so it is a sensible default. Use 3.0 if a critical tool in your pipeline does not yet support 3.1, and check tool support before adopting 3.2.
Should generated client code be committed?
Either approach works. Committing makes changes visible in review and keeps builds simple; generating in CI keeps repositories smaller. Whichever you choose, pin the generator version and never hand-edit generated files.
Does OpenAPI work for GraphQL, gRPC, or events?
No. GraphQL APIs use their own schema, gRPC uses Protocol Buffers definitions, and event-driven APIs are described with AsyncAPI. The same principles — a single source of truth, generation, and breaking-change checks — apply to each.
Related Topics
- Migration Guides and Breaking Changes — Explain affected users, required action, compatibility windows, and verification steps.
- APIs & Integration — API styles, gateways, and integration patterns
- API Documentation — Turning the contract into docs developers use
- API Design — Consistent resources, errors, and pagination
- API Versioning — Shipping deliberate breaking changes
- Contract Testing — Verifying consumers and providers agree
- FastAPI — Code-first OpenAPI generation in Python
- Property-Based Testing — The idea behind schema-driven API fuzzing
- AsyncAPI — The event-driven counterpart to OpenAPI