Most API bugs are not coding mistakes. They are agreement mistakes. The frontend expected userId, the backend sent user_id, and nobody noticed until QA. Spec-first API development fixes this by making the contract the first thing you write, not the last thing you document.
In this guide you will write a small OpenAPI spec by hand, then use that single file to generate mocks, tests, and docs before any server code exists. The same approach goes by a few names: spec-driven development, design-first, or contract-first. They all point to the same idea. Agree on the interface first, then build to it.
What spec-first API development is
Spec-first API development means you author a machine-readable contract, usually an OpenAPI document, before you implement the endpoint. That contract describes every path, parameter, request body, response shape, and status code. Once it exists, it becomes the source of truth for everyone touching the API.
The spec is not documentation that trails behind the code. It leads. Frontend teams build against a mock generated from it. QA writes tests against it. The backend implements to satisfy it. When all three agree with the same file, integration stops being a guessing game.
This flips the usual order. In code-first development, you write handlers and then describe them afterward, often by hand, often out of date within a sprint. In a design-first workflow, the description comes first and the code answers to it. That single change moves most integration problems to the start of the project, where they are cheap to fix.
Spec-first vs code-first lifecycle
The two approaches produce the same endpoints. They differ in when problems surface and who can start work in parallel.

The right-hand column is the payoff. When the contract exists first, three teams stop blocking each other and start building against one shared definition.
A worked OpenAPI example
Let’s design a small /users endpoint step by step. We will build it in pieces so each part is clear, then assemble the full file.
Start with the document header. This declares the OpenAPI version and basic metadata.
openapi: 3.0.3
info:
title: Users API
version: 1.0.0
servers:
- url: https://api.example.com/v1
Next, define the User schema under components. Defining it once lets you reference it everywhere, so the shape stays consistent across requests and responses.
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
Now add the GET /users path. It lists users and supports a limit query parameter. Notice how the response reuses the User schema with $ref instead of redefining it.
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"
Add the POST /users operation to create a user. The request body defines what the client must send, and the 201 response returns the created record.
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
That is a complete, valid contract. It names every field, marks email as required, caps limit at 100, and declares both the success and error responses. Nobody has written a line of server code yet, but the agreement is locked.
Generate mocks, tests, and docs from the spec
The reason to write the spec first is leverage. One file drives three artifacts that teams usually build separately.
Mocks. A mock server reads your spec and returns example responses that match every schema. The format: uuid and format: email hints let tooling generate realistic sample data. Your frontend calls GET /users and gets a well-formed array of users on day one, long before the real handler exists. When the spec changes, the mock changes with it.
Tests. Because the spec declares required fields, types, and status codes, it doubles as a test oracle. Contract tests assert that the real response for POST /users returns a 201 with a body matching the User schema, and a 400 when email is missing. You are not inventing assertions. You are checking that the implementation matches what you already agreed to.
Docs. API reference documentation renders directly from the spec. Every endpoint, parameter, and example you see in the docs comes from the same YAML. There is no second copy to keep in sync, so the docs cannot drift from the contract.
This is also what makes spec-first pair well with a git-native API workflow: the spec is a plain text file, so every change to the contract shows up as a reviewable diff in a pull request. A reviewer can see that someone renamed email or dropped a required field before it ships.
Doing it in Apidog
Apidog supports this end to end through Spec-First Mode. Instead of treating the OpenAPI file as an export, it treats the file as the project. You edit the YAML directly and the rest of the workspace follows.

You write or paste the /users spec into the editor. Apidog parses it and immediately stands up a mock server for both operations, so your frontend can start calling them. The generated documentation updates as you type. When you are ready to verify the implementation, you run the spec’s operations as test cases against your real backend and confirm the responses match the schemas.
The part that keeps this honest is two-way Git sync. Your spec lives in a repository, and changes flow in both directions. Edit the YAML in your editor and push, and Apidog picks it up. Edit in Apidog, and the change lands as a commit your team can review. The contract never lives in two places at once. If you want the deeper comparison of where this sits next to a pure design-first approach, see spec-first vs design-first in Apidog.
A spec-first checklist
Use this before you call a spec ready to build against:
- The spec validates against the OpenAPI schema with no errors.
- Every endpoint declares its success and at least one error response.
- Shared shapes live in
components/schemasand are referenced with$ref, not copied. - Required fields are marked
required, and formats (uuid,email,date-time) are set where they apply. - The spec file is committed to version control and reviewed in pull requests.
- A mock server runs from the spec, and the frontend can hit it.
- Contract tests check real responses against the declared schemas.
- The published docs render from the same file, with no hand-maintained copy.
If every box is checked, your teams can build in parallel against one agreement instead of three guesses.
FAQ
Is spec-first API development the same as design-first?
Mostly yes. “Design-first” and “contract-first” describe the same principle: write the interface before the implementation. “Spec-first” is the most literal name because the OpenAPI spec file is the concrete artifact you start with. In practice the terms are used interchangeably.
Do I have to write YAML by hand?
No. You can author the spec in a visual editor and let it produce the YAML, or write the YAML directly if you prefer. The point is not the format you type, it is that the contract exists and is agreed upon before code. Many teams mix both, drafting visually and refining in YAML during review.
How do I stop the spec and the code from drifting apart?
Make the spec the source of truth and enforce it. Keep the spec in Git so changes are reviewed as diffs. Run contract tests in CI so the build fails when a response stops matching the schema. With two-way sync, edits in either place stay reconciled, which removes the most common cause of drift.
Spec-first API development is a small change in order with a large change in outcome. Write the contract, agree on it, then build to it. If you want to try the full loop, open Spec-First Mode in Apidog and point it at your repo.



