Most API bugs are contract bugs, not implementation bugs. The frontend expects userId, the backend returns user_id, and nobody catches it until QA. Spec-first API development prevents this by making the API contract the first artifact you create, review, and build against.
In this guide, you will define a small OpenAPI contract, then use that same file to drive mocks, tests, and documentation before writing server code. This workflow is also called spec-driven, design-first, or contract-first API development. The implementation detail varies, but the rule is the same: agree on the interface first, then build to it.
What spec-first API development means
Spec-first API development means you write a machine-readable API contract before implementing the endpoint. In most teams, that contract is an OpenAPI document.
The spec defines:
- Paths
- HTTP methods
- Query parameters
- Request bodies
- Response bodies
- Status codes
- Shared schemas
- Validation rules
The spec is not documentation generated after the code is done. It is the source of truth.
That lets each team start earlier:
- Frontend developers build against a mock server generated from the spec.
- QA engineers create contract tests from the expected responses.
- Backend developers implement handlers that satisfy the contract.
- Documentation stays aligned because it renders from the same file.
In a code-first workflow, the API is implemented first and documented later. That documentation can drift quickly. In a spec-first workflow, the API description comes first and the implementation must conform to it.
Spec-first vs code-first lifecycle
Both approaches can produce the same API. The difference is when integration problems appear.
With spec-first development, the contract exists before implementation. That makes it possible for frontend, backend, and QA teams to work in parallel against one shared definition.
Build a small OpenAPI spec
Let’s define a simple /users API.
The API will support:
-
GET /usersto list users -
POST /usersto create a user
Start with the OpenAPI document header:
openapi: 3.0.3
info:
title: Users API
version: 1.0.0
servers:
- url: https://api.example.com/v1
This declares the OpenAPI version, API metadata, and base server URL.
Next, define a reusable User schema under components/schemas:
components:
schemas:
User:
type: object
required: [id, email, createdAt]
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
createdAt:
type: string
format: date-time
This schema becomes the shared definition for a user object. Any endpoint can reference it with $ref, which avoids duplicated response definitions.
Now define GET /users:
paths:
/users:
get:
summary: List users
operationId: listUsers
parameters:
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
"200":
description: A list of users
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
This contract says:
-
limitis an optional query parameter. -
limitmust be an integer. - The default value is
20. - The maximum value is
100. - A successful response returns an array of
Userobjects.
Now add POST /users:
post:
summary: Create a user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email]
properties:
email:
type: string
format: email
name:
type: string
responses:
"201":
description: User created
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"400":
description: Invalid request body
This operation defines:
- A required JSON request body
- A required
emailfield - An optional
namefield - A
201response containing the createdUser - A
400response for invalid input
At this point, you have a complete API contract. No server code exists yet, but the expected behavior is already defined.
Full OpenAPI example
Here is the full file assembled:
openapi: 3.0.3
info:
title: Users API
version: 1.0.0
servers:
- url: https://api.example.com/v1
paths:
/users:
get:
summary: List users
operationId: listUsers
parameters:
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
"200":
description: A list of users
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
post:
summary: Create a user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email]
properties:
email:
type: string
format: email
name:
type: string
responses:
"201":
description: User created
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"400":
description: Invalid request body
components:
schemas:
User:
type: object
required: [id, email, createdAt]
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
createdAt:
type: string
format: date-time
This file now defines the contract for implementation, mocking, testing, and documentation.
Generate mocks, tests, and docs from the spec
The main benefit of spec-first development is reuse. One OpenAPI file can drive several parts of your API workflow.
1. Generate a mock API
A mock server can read the OpenAPI spec and return responses that match the declared schemas.
For example, GET /users can return an array of users shaped like this:
[
{
"id": "2f7f4f02-0a84-41c4-b1c7-8e7f5a79c111",
"email": "alex@example.com",
"name": "Alex",
"createdAt": "2026-06-02T10:15:30Z"
}
]
The frontend can now integrate against the mock endpoint immediately.
That means frontend work does not need to wait for backend implementation. If the spec changes, the mock changes with it.
2. Create contract tests
The same spec also works as a test oracle.
For POST /users, contract tests should verify that:
- A valid request returns
201. - The response body matches
#/components/schemas/User. - A request without
emailreturns400. - Returned fields use the expected names and types.
Example test expectations:
POST /users
Request body:
{
"email": "alex@example.com",
"name": "Alex"
}
Expected:
- status code: 201
- response content-type: application/json
- response body matches User schema
You are not inventing test assertions separately. You are checking whether the implementation matches the contract the team already approved.
3. Publish API docs
API reference documentation can render directly from the OpenAPI file.
That keeps docs aligned with the actual contract because:
- Endpoint paths come from the spec.
- Parameters come from the spec.
- Request bodies come from the spec.
- Response schemas come from the spec.
- Status codes come from the spec.
There is no separate document to maintain manually.
This also fits well with a git-native API workflow. Since the OpenAPI spec is plain text, every API change becomes a reviewable diff in a pull request. Reviewers can catch breaking changes like renamed fields, removed required properties, or changed response structures before they ship.
Doing it in Apidog
Apidog supports this workflow through Spec-First Mode. Instead of treating OpenAPI as an export artifact, Apidog treats the OpenAPI file as the project source.
A practical workflow looks like this:
- Create or open an OpenAPI file.
- Define paths, schemas, request bodies, and responses.
- Paste or edit the spec in Apidog.
- Use the generated mock server while the backend is still being built.
- Share generated API documentation with the team.
- Run the operations as test cases against the real backend.
- Verify that actual responses match the declared schemas.
Apidog also supports two-way Git sync. The spec can live in your repository, and changes can flow between Git and Apidog.
That means:
- If you edit the YAML locally and push it, Apidog can pick up the change.
- If you edit the API in Apidog, the change can be committed back for review.
- The contract remains reviewable through Git instead of becoming a separate source of truth.
For a deeper comparison, see spec-first vs design-first in Apidog.
Spec-first implementation checklist
Before backend work starts, check the spec against this list:
- The OpenAPI document validates with no schema errors.
- Every endpoint has a clear
operationId. - Every endpoint declares success responses.
- Every endpoint declares at least one error response.
- Request bodies define required fields.
- Response bodies reference shared schemas where possible.
- Reusable objects live in
components/schemas. - Shared schemas are referenced with
$refinstead of copied. - Formats such as
uuid,email, anddate-timeare set where relevant. - Query parameters define types, defaults, and limits where needed.
- The spec is committed to version control.
- API changes are reviewed through pull requests.
- A mock server can run from the spec.
- Frontend developers can call the mock API.
- Contract tests compare real backend responses against the spec.
- Published documentation renders from the same OpenAPI file.
If all of these are true, the team can build against one shared contract instead of separate assumptions.
FAQ
Is spec-first API development the same as design-first?
Mostly yes. “Design-first” and “contract-first” describe the same principle: define the interface before implementing it.
“Spec-first” is more specific because the OpenAPI spec file is the artifact you start from. In practice, teams often use these terms interchangeably.
Do I have to write YAML by hand?
No.
You can use a visual editor to produce the OpenAPI file, or you can write YAML directly. The important part is not the editing method. The important part is that the contract exists, is reviewed, and is agreed on before implementation starts.
Many teams use both: visual editing for initial design and direct YAML review in pull requests.
How do I stop the spec and code from drifting apart?
Make the spec the source of truth and enforce it in your workflow.
Use these controls:
- Keep the spec in Git.
- Review API changes as pull requests.
- Run contract tests in CI.
- Fail builds when responses no longer match the schema.
- Keep generated docs tied to the same OpenAPI file.
- Use two-way sync if your API platform supports it.
Drift usually happens when the API contract, implementation, tests, and docs are maintained separately. Spec-first development reduces that by making one file drive the workflow.
Spec-first API development is a small change in sequence with a large impact on delivery. Write the contract, review it, generate mocks and docs from it, then implement against it. To try the full workflow, open Spec-First Mode in Apidog and connect it to your repo.


Top comments (0)