Contract-First Design

Standards Jan 9, 2026 YAML
contract-first api-design design-methodology openapi specification

Definition

Contract-first design is an API development approach where you start by defining the complete API contract - endpoints, request/response schemas, authentication, error handling - in a formal specification (usually OpenAPI) before writing any implementation code. This inverts the traditional code-first approach where you build the API first and document it afterward.

The philosophy behind contract-first is that API design is a communication problem, not a coding problem. By creating the contract first, you force upfront discussions about naming, data models, error handling, and edge cases. Stakeholders (frontend developers, mobile teams, partners) review the contract and provide feedback before implementation begins. This catches design issues early when they’re cheap to fix.

Contract-first enables true parallel development. Backend engineers implement the contract while frontend engineers build against mock servers generated from the same contract. Integration happens faster because both sides are coding to an agreed specification. Tools validate that implementations match the contract, preventing drift between docs and reality.

Example

Microservices Team: A team building a new payment microservice starts with an OpenAPI spec defining all endpoints, schemas, and error codes. Product managers, frontend engineers, and backend engineers collaboratively review the spec in design sessions. They identify issues like missing pagination on list endpoints and unclear error responses. After approval, backend implements the spec while frontend generates TypeScript types from it and builds UI against mock servers.

Public API Launch: Stripe designs new API features as OpenAPI specs first. They share specs with design partners (select customers) who provide feedback on usability, completeness, and edge cases before Stripe writes implementation code. This early feedback prevents costly changes after launch.

Mobile App + Backend: A startup building iOS, Android, and web apps starts each sprint by defining API changes as OpenAPI specs. All three client teams immediately generate SDKs from the spec and start building features using mock servers. Backend implements the spec in parallel. When backend finishes, clients swap mock servers for real endpoints with zero code changes.

Enterprise Integration: A large company exposing internal APIs to partners starts with contract-first. Legal, security, and engineering teams review the OpenAPI spec together, ensuring compliance, proper error handling, and security before implementation. The spec becomes the formal agreement with partners.

Breaking Change Prevention: A team wants to add a required field to an existing endpoint. During spec review (before coding), contract diffing tools flag this as a breaking change. The team pivots to making it optional with a default value, preserving backward compatibility.

Analogy

Architectural Blueprints: You don’t start building a house by pouring concrete randomly, then drawing blueprints afterward to document what you built. Architects create detailed blueprints first, get approval from all stakeholders (client, city inspectors, contractors), then construction begins. Contract-first is the same - design the blueprint (API spec), get buy-in, then implement.

Movie Screenplay: Filmmakers write the screenplay before filming. Actors, directors, producers review it, suggest changes, estimate budget. Only after the screenplay is approved does filming begin. Code-first would be like improvising the entire movie during filming, then writing a screenplay to document what happened.

Legal Contracts: When two companies form a partnership, lawyers draft a contract first, both sides negotiate terms, and only after signing do they start working together. No one builds products first then writes a contract describing what they built. API contract-first follows the same principle - agree on terms before investing in implementation.

Code Example

Step 1: Define Contract (OpenAPI Spec)


# contract/api-spec.yaml - Created BEFORE implementation
openapi: 3.1.0
info:
  title: Task Management API
  version: 1.0.0
  description: API for managing tasks in a project

paths:
  /tasks:
    get:
      summary: List all tasks
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [open, in_progress, completed]
        - name: assignee
          in: query
          schema:
            type: string
      responses:
        '200':
          description: List of tasks
          content:
            application/json:
              schema:
                type: object
                properties:
                  tasks:
                    type: array
                    items:
                      $ref: '#/components/schemas/Task'
                  totalCount:
                    type: integer

    post:
      summary: Create a new task
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTaskRequest'
      responses:
        '201':
          description: Task created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Task'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

components:
  schemas:
    Task:
      type: object
      required: [id, title, status, createdAt]
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
          maxLength: 200
        description:
          type: string
        status:
          type: string
          enum: [open, in_progress, completed]
        assignee:
          type: string
          format: email
        createdAt:
          type: string
          format: date-time

    CreateTaskRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type: string
        assignee:
          type: string
          format: email

    Error:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
        message:
          type: string

Step 2: Generate Mock Server (runs immediately, before backend exists)


# Using Prism to create mock server from contract
npx @stoplight/prism-cli mock contract/api-spec.yaml

# Mock server runs at http://localhost:4010
# Frontend can now develop against it immediately

Step 3: Frontend Development (parallel with backend)


// Generate TypeScript types from OpenAPI spec
// Using openapi-typescript
import type { paths } from './generated/api-types';

type TaskListResponse = paths['/tasks']['get']['responses']['200']['content']['application/json'];
type CreateTaskRequest = paths['/tasks']['post']['requestBody']['content']['application/json'];

// Use generated types for type-safe API calls
async function fetchTasks(status?: string): Promise<TaskListResponse> {
  const response = await fetch(`http://localhost:4010/tasks?status=${status || ''}`);
  return response.json(); // TypeScript knows exact shape
}

async function createTask(data: CreateTaskRequest) {
  const response = await fetch('http://localhost:4010/tasks', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  return response.json();
}

// Using the typed functions
const tasks = await fetchTasks('open'); // TypeScript knows tasks structure
const newTask = await createTask({
  title: "Implement backend",
  assignee: "[email protected]"
}); // TypeScript validates this object matches schema

Step 4: Backend Implementation (validates against contract)


// Backend implements the contract
const express = require('express');
const { OpenApiValidator } = require('express-openapi-validator');

const app = express();

// Automatically validate requests/responses against contract
app.use(OpenApiValidator.middleware({
  apiSpec: './contract/api-spec.yaml',
  validateRequests: true,
  validateResponses: true
}));

app.get('/tasks', (req, res) => {
  const { status, assignee } = req.query;
  // Implementation...

  const tasks = [
    { id: '123', title: 'Task 1', status: 'open', createdAt: '2024-01-01T10:00:00Z' }
  ];

  // Validator ensures response matches contract schema
  res.json({ tasks, totalCount: tasks.length });
});

app.post('/tasks', (req, res) => {
  // Validator already checked req.body matches CreateTaskRequest schema
  const newTask = {
    id: generateUUID(),
    ...req.body,
    status: 'open',
    createdAt: new Date().toISOString()
  };

  // Validator ensures response matches Task schema
  res.status(201).json(newTask);
});

// If response doesn't match schema, validator returns 500 and logs error
app.listen(3000);

Diagram

graph TB
    subgraph "Contract-First Flow"
        START[Define Contract
OpenAPI Spec] START --> REVIEW[Stakeholder Review
Frontend, Backend, Product] REVIEW --> ITERATE{Changes
Needed?} ITERATE -->|Yes| START ITERATE -->|No| APPROVE[Contract Approved] APPROVE --> PARALLEL[Parallel Development] subgraph "Frontend Track" PARALLEL --> GEN_SDK[Generate SDK
TypeScript Types] GEN_SDK --> MOCK[Mock Server
From Contract] MOCK --> FE_DEV[Frontend Development] end subgraph "Backend Track" PARALLEL --> BE_DEV[Backend Implementation] BE_DEV --> CONTRACT_TEST[Contract Tests
Validate Match] end FE_DEV --> INTEGRATION CONTRACT_TEST --> INTEGRATION[Integration
Swap Mock for Real API] end style START fill:#90EE90 style APPROVE fill:#FFD700 style INTEGRATION fill:#87CEEB

Best Practices

  1. Involve all stakeholders early - Frontend, backend, product, security review the spec together
  2. Use spec linting tools - Run Spectral or similar to enforce design standards on specs
  3. Generate everything from contract - SDKs, mock servers, tests, docs all come from the spec
  4. Version the contract - Use semantic versioning, track breaking vs. non-breaking changes
  5. Automate contract testing - CI/CD validates implementation matches contract on every commit
  6. Design for evolution - Use extensible patterns (additionalProperties, optional fields) for future growth
  7. Review before implementation - Catch design flaws in spec review, not during coding
  8. Mock server in development - Frontend uses contract-based mocks until backend is ready

Common Mistakes

Spec as afterthought: Creating a “contract-first” spec after implementation is done. This defeats the purpose - you miss the design benefits and parallel development.

Skipping stakeholder review: Backend writes the spec alone, doesn’t involve frontend or product. They discover usability issues only after implementation.

Not enforcing contract: Creating a spec but not validating implementation against it. Contract and code drift over time.

Perfect spec paralysis: Spending weeks perfecting the spec before allowing implementation. Iterate quickly, get to working code faster.

Ignoring generated SDKs: Writing the spec but hand-coding clients anyway. This wastes the main benefit of contract-first.

No contract testing: Trusting implementation matches spec without automated validation. Manual testing misses subtle schema violations.

One-time contract: Creating the spec for v1, then abandoning it for future changes. Contract must evolve with the API.

Standards & RFCs

Standards & RFCs
1)- [OpenAPI 3](https://reference.apios.info/terms/openapi-3/).1.0 Specification
2)- JSON [Schema](https://reference.apios.info/terms/schema/) Draft 2020-12
3)- Spectral (OpenAPI linting)
4)- Dredd (contract testing)