DEV Community

Cover image for What Is Spec-First API Development?
Hassann
Hassann

Posted on • Originally published at apidog.com

What Is Spec-First API Development?

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.

Try Apidog today

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.

Spec-first vs code-first lifecycle

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 /users to list users
  • POST /users to 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

This contract says:

  • limit is an optional query parameter.
  • limit must be an integer.
  • The default value is 20.
  • The maximum value is 100.
  • A successful response returns an array of User objects.

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
Enter fullscreen mode Exit fullscreen mode

This operation defines:

  • A required JSON request body
  • A required email field
  • An optional name field
  • A 201 response containing the created User
  • A 400 response 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
Enter fullscreen mode Exit fullscreen mode

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"
  }
]
Enter fullscreen mode Exit fullscreen mode

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 email returns 400.
  • 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
Enter fullscreen mode Exit fullscreen mode

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.

Apidog Spec-First Mode

A practical workflow looks like this:

  1. Create or open an OpenAPI file.
  2. Define paths, schemas, request bodies, and responses.
  3. Paste or edit the spec in Apidog.
  4. Use the generated mock server while the backend is still being built.
  5. Share generated API documentation with the team.
  6. Run the operations as test cases against the real backend.
  7. 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 $ref instead of copied.
  • Formats such as uuid, email, and date-time are 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)