Usually an API is born from code: you write the controllers, and the description (if there is one) gets generated afterwards. API-first flips the order: first we agree on the contract, and only then do we write the code against it. Let's look at why and how this works.
What API-first means in plain terms
API-first is an approach in which the API contract is designed first, before the implementation, and becomes the primary artifact everyone works against. The contract is a machine-readable description: which endpoints exist, which parameters and request bodies they take, which responses and error codes they return. The standard format for such a description in REST is OpenAPI (YAML or JSON).
Short formula: contract first, code second. The contract is the agreement between those who provide the API and those who consume it.
The opposite is code-first: we write the code and derive the API description from it (annotations, reflection). Both variants produce an OpenAPI document in the end, but the order and the source of truth differ.
Why you need it
When the contract is ready before the code, it delivers value right away:
- Parallel work. Frontend and backend don't wait for each other: both take the agreed contract and work simultaneously. The frontend spins up a mock (stub) from the spec and builds the interface while the backend implements the logic.
- Single source of truth. There is one contract, and it is machine-readable. There is no "the code says one thing, the docs say another" — the documentation is generated from the same contract.
- Code generation. DTOs, controller interfaces (server stubs), and clients are generated from OpenAPI. Less hand-written boilerplate and fewer discrepancies between the sides.
- Early design conversation. The contract is easy to read and discuss in review before a single line of the implementation exists. Fixing YAML is cheaper than rewriting finished code.
- Compatibility checks. The contract can be compared automatically across versions to catch breaking changes in CI.
Contract-first and code-first
Both paths lead to an OpenAPI document, but in different ways.
Contract-first — the source of truth is the OpenAPI YAML. First we write the spec by hand, then openapi-generator produces DTOs and interfaces from it. The controller implements the generated interface (implements <Tag>Api), and validation constraints (@NotNull, formats, lengths) land in the DTOs automatically from the YAML.
# contract fragment: the contract comes first
paths:
/orders/{id}:
get:
operationId: getOrder
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
responses:
"200":
description: Order
content:
application/json:
schema: { $ref: "#/components/schemas/Order" }
Code-first — the source of truth is the code: DTO classes and annotations from which the API description is generated automatically (in Spring it's springdoc, in FastAPI it's Pydantic models, in Go it's struct tags). Faster to start with, but here the contract is a byproduct of the code, not an agreement.
Which to choose: for a public API and for several teams that need to agree in advance, contract-first is usually better — the contract is visible and stable before implementation. For a small internal service written by a single team, code-first is simpler and good enough.
What the contract-first process looks like
- Write/edit the OpenAPI contract — endpoints, schemas, errors, versions.
- Review the contract — discuss the design at the YAML level, before any code.
- Generation —
openapi-generatorproduces DTOs + controller interfaces for the server and a client for the consumer. - Implementation — the controller implements the generated interface; logic is written inside, the contract is not rewritten by hand.
- CI checks — lint the contract and verify backward compatibility with the previous version.
An important team rule: contract edits go into the YAML, not into the generated code. Generated files are overwritten on every build — editing them by hand is pointless.
Mocks and parallel work
The main practical win of API-first is a mock straight from the contract. Tools (Prism, for example) spin up a fake server from the OpenAPI file: it responds with examples from the spec. Frontend and consumer services start integrating immediately, without waiting for a finished backend. When the real service is ready, they switch from the mock to it — the contract is the same one.
Versioning and contract evolution
The contract lives a long time, and you need to change it without breaking consumers. Basic rules: adding optional fields is fine; removing or renaming existing ones is a breaking change that requires a new version. The contract is a convenient place where this is visible: a diff of two YAML files immediately shows what changed. More on versions in a separate article below.
When API-first is overkill
The approach isn't free: the contract has to be maintained, and generation adds a step to the build. For a throwaway prototype or a tiny internal endpoint it's too much — code-first is faster there. API-first pays off when the API will outlive a single sprint, is consumed by more than one team, or is public.
In short
- API-first = contract first (OpenAPI), then code against it.
- It enables parallel frontend and backend work, a single source of truth, code generation, and mocks from the spec.
- Contract-first — the source of truth is the YAML (we generate code from it); code-first — the source of truth is the code (we generate the description from it).
- Contract-first is good for public APIs and multiple teams; code-first is good for small internal services.
- Contract edits go into the YAML, not the generated code.
- Contract versions: adding optional things is fine; removing/renaming is a breaking change.
What to read next
- OpenAPI: metadata and common mistakes in REST — the contract format itself: operationId, tags, parameters, and frequent mistakes.
- REST API versioning — how to change the contract without breaking consumers.
- URLs and resources in REST — what makes up a well-designed contract.