6 min read
On this page

API-First Design

API-first design means designing the API before writing any implementation code. The API specification becomes the contract between teams. Frontend developers mock against it. Backend developers implement to it. QA tests against it. Everyone agrees on what the API looks like before anyone writes a line of business logic.

This is the opposite of the common pattern where the backend team builds the service, then exposes whatever internal data structures happen to exist as the API. That pattern produces APIs that mirror database schemas, leak implementation details, and change every time the backend refactors.

The Core Idea

Write the API specification first. Review it with consumers. Get agreement. Then build.

An OpenAPI specification for a simple order endpoint:

{
  "openapi": "3.1.0",
  "info": {
    "title": "Order Service",
    "version": "1.0.0"
  },
  "paths": {
    "/orders": {
      "post": {
        "summary": "Create an order",
        "operationId": "createOrder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["customer_id", "items", "currency"],
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "The ID of the customer placing the order"
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": ["product_id", "quantity"],
                      "properties": {
                        "product_id": { "type": "string" },
                        "quantity": { "type": "integer", "minimum": 1 }
                      }
                    }
                  },
                  "currency": {
                    "type": "string",
                    "enum": ["usd", "eur", "gbp"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Order created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request"
          },
          "422": {
            "description": "Unprocessable — validation failed"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Order": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "customer_id": { "type": "string" },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LineItem"
            }
          },
          "total_cents": { "type": "integer" },
          "currency": { "type": "string" },
          "status": {
            "type": "string",
            "enum": ["pending", "confirmed", "shipped", "delivered"]
          },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "LineItem": {
        "type": "object",
        "properties": {
          "product_id": { "type": "string" },
          "quantity": { "type": "integer" },
          "unit_price_cents": { "type": "integer" }
        }
      }
    }
  }
}

This specification is a contract. Frontend teams know exactly what request to send and what response to expect. Backend teams know exactly what to implement. The specification exists before either side writes code.

The API-First Workflow

Step 1: Design the API

The team (or an API designer) writes the specification. This involves naming resources, choosing HTTP methods, defining request/response shapes, and documenting error cases. This is a design activity, not a coding activity.

Step 2: Review with Consumers

Share the specification with the teams that will consume the API. Frontend developers, mobile developers, and partner teams review it. They ask: "Can I build what I need with this contract?" This review catches mismatches early, when changing the spec costs nothing.

Step 3: Generate Mock Servers

Tools like Prism, Stoplight, and Postman can generate mock servers from an OpenAPI spec. Frontend teams start building against mocks immediately:

$ prism mock openapi.yaml
[INFO] Mock server is running on http://127.0.0.1:4010

$ curl http://127.0.0.1:4010/orders -X POST \
    -H "Content-Type: application/json" \
    -d '{"customer_id": "cus_123", "items": [{"product_id": "prod_456", "quantity": 2}], "currency": "usd"}'

{
  "id": "ord_mock_789",
  "customer_id": "cus_123",
  "items": [...],
  "total_cents": 5000,
  "currency": "usd",
  "status": "pending",
  "created_at": "2024-01-15T12:00:00Z"
}

The frontend team builds real UI components against realistic mock data while the backend team builds the real implementation.

Step 4: Implement in Parallel

Backend and frontend develop simultaneously. The backend team implements the endpoints to match the spec. The frontend team builds against mocks. Both teams are working toward the same contract.

Step 5: Contract Testing

When both sides are ready, contract tests verify that the real implementation matches the specification. Tools like Schemathesis (for OpenAPI) or Pact (for consumer-driven contracts) automate this.

$ schemathesis run --url http://localhost:8080 openapi.yaml
...
GET /orders ................ PASSED
POST /orders ............... PASSED
GET /orders/{order_id} ..... PASSED
PATCH /orders/{order_id} ... PASSED

All checks passed.

Consumer-Driven Contract Testing

Consumer-driven contracts flip the direction. Instead of the provider defining the contract, the consumer defines what it needs, and the provider verifies it can fulfill those needs.

The flow works like this:

  1. The frontend team writes a Pact file describing the requests it will make and the responses it expects
  2. The Pact file is shared with the backend team
  3. The backend runs the Pact against its real implementation
  4. If the Pact passes, the contract is satisfied

This catches a common problem: the provider changes a field name that no one thought was in use, but three consumers depend on it. Consumer-driven contracts make those dependencies explicit.

The Benefits

Parallel Development

Without API-first design, the frontend team waits for the backend team to finish. With API-first design, both teams start on day one. The mock server unblocks frontend work completely.

Better Documentation

The OpenAPI specification is the documentation. It is machine-readable, so you can generate interactive docs (Swagger UI, Redoc), client SDKs (openapi-generator), and test suites from the same source file. The documentation never drifts from the implementation because the implementation is tested against the spec.

Fewer Integration Surprises

The most expensive bugs are discovered during integration. "The field is called user_id, not userId" should not be found in the last week of a sprint. API-first design surfaces these mismatches during the review step, when fixing them costs a five-minute spec change.

Consistent Design

When the spec is reviewed before implementation, you catch inconsistencies. Why does the user endpoint return created_at but the order endpoint returns creation_date? Why does one endpoint use pagination cursors and another uses page numbers? These questions get asked and resolved before code is written.

API Design Reviews

Treat API design reviews with the same seriousness as code reviews. The API is the interface that external and internal consumers depend on. A bad function name can be refactored; a bad endpoint name is permanent.

A design review checklist:

  • Naming — are resources nouns? Are they consistent with existing endpoints?
  • HTTP methods — does the method match the operation? No side effects on GET?
  • Request/response shapes — are field names consistent with the rest of the API?
  • Error responses — does the error format match the API-wide standard?
  • Pagination — does this list endpoint use the same pagination pattern as others?
  • Versioning — does this change require a new API version?
  • Security — are authorization rules defined? Are sensitive fields excluded?

Stripe, Twilio, and GitHub all have internal API review processes. At Stripe, every new API endpoint goes through a design review with a dedicated API platform team before any implementation begins.

Real-World Example: Twilio

Twilio designs their API before building services. Their API design guide is public and covers naming conventions, error formats, pagination, and versioning. Every Twilio product (SMS, Voice, Video, Email) follows the same patterns because the design is centralized and reviewed.

The result: a developer who has integrated Twilio SMS can integrate Twilio Video without re-learning the API style. The consistency across products is not accidental — it is the outcome of API-first design enforced through reviews.

Common Pitfalls

  • Spec as afterthought — writing the code first and generating the spec from the implementation. This produces specs that describe the implementation rather than define the contract.
  • Skipping consumer review — designing the API without input from the teams that will consume it. The producer's intuition about what consumers need is usually wrong.
  • Mock server neglect — generating mocks once and never updating them as the spec evolves. Stale mocks cause the same integration surprises API-first design is meant to prevent.
  • Over-specifying — defining every possible endpoint, filter, and expansion before any consumer has asked for it. Design the API you need now, not the API you might need in two years.
  • No enforcement — having a spec but not testing the implementation against it. Without contract tests, the spec and the implementation drift apart within weeks.
  • Design-by-committee — involving too many stakeholders in the design review, leading to compromises that make the API inconsistent. One person or a small team should own the API design, with input from consumers.

Key Takeaways

  • Design the API before writing code. The specification is the contract between producer and consumer teams.
  • Use OpenAPI (or GraphQL SDL, or .proto files) as the specification format. It is machine-readable, so you get mocks, docs, SDKs, and tests from the same source.
  • Generate mock servers from the spec so frontend teams can build without waiting for the backend.
  • Run contract tests to verify the implementation matches the specification. Automated, not manual.
  • Treat API design reviews as a required step in the development process. The API is the most expensive interface to change after launch.
  • Consumer-driven contract testing makes dependencies between services explicit and catches breaking changes before they reach production.