Definition
An API contract is a formal, machine-readable specification that precisely defines how an API works. It describes every endpoint, what inputs each accepts (request schemas), what outputs each returns (response schemas), authentication requirements, error codes, rate limits, and behavioral guarantees. Think of it as a legal contract between API provider and consumer - it specifies exactly what both parties can expect from the interaction.
Unlike informal documentation that might describe an API in prose, a contract is unambiguous and enforceable. Tools can validate that implementations actually match the contract, that client code correctly uses the API, and that changes to either side maintain backward compatibility. Contracts enable parallel development - backend teams implement the contract while frontend teams code against it simultaneously, using mock servers that follow the contract.
In practice, API contracts are typically written as OpenAPI specifications, GraphQL schemas, Protocol Buffers, or similar formats. The contract becomes the single source of truth - documentation is generated from it, SDKs are generated from it, tests validate against it, and API gateways enforce it.
Example
Microservices Communication: A payment service and order service need to communicate. Before writing code, teams define a contract: POST /payments accepts orderId (UUID), amount (number), currency (3-letter code), returns paymentId and status. Both teams code to this contract. Integration tests validate actual API calls match the contract.
Third-Party API Integration: Stripe publishes their API contract as an OpenAPI spec. When you integrate Stripe payments, you download their contract, generate a type-safe SDK from it, and your IDE autocompletes method calls with correct parameters. If Stripe changes their contract (breaking change), your SDK generation fails, alerting you immediately.
Mobile App Development: A team building iOS and Android apps needs a backend API. Backend defines the contract first: GET /users/{id} returns User object with specific fields. Mobile teams immediately start building UI using the contract to generate mock data, without waiting for backend implementation.
SLA Enforcement: A contract specifies that GET /products must respond within 200ms at 99th percentile, handle 10,000 requests/second, and maintain 99.9% uptime. Monitoring tools validate the live API against these contract terms, triggering alerts when SLA violations occur.
Breaking Change Detection: A team wants to rename “userId” to “id” in API responses. Contract testing tools compare the proposed change against the existing contract and flag it as a breaking change - existing clients expect userId. The team either keeps userId for backward compatibility or versions the API properly.
Analogy
The Building Blueprint: Before constructing a building, architects create detailed blueprints specifying every room’s dimensions, door placements, electrical outlets, plumbing connections. Contractors from different trades (electrical, plumbing, framing) work simultaneously using the blueprint as the contract. Inspectors verify work matches the blueprint. An API contract is the same - it’s the blueprint that multiple teams follow to ensure everything connects correctly.
The Restaurant Menu: A menu is a contract between restaurant and customer. It specifies what dishes are available (endpoints), what’s in each dish (request/response schemas), prices (rate limits), and preparation time (SLAs). Customers order based on the menu contract. If the kitchen changes a recipe significantly without updating the menu, customers who ordered based on the old contract will be surprised.
The Rental Agreement: When renting an apartment, you sign a contract specifying rent amount, due date, maintenance responsibilities, termination conditions. Both landlord and tenant know exactly what’s expected. If the landlord suddenly changes rent mid-lease without contract amendment, they’re in breach. API contracts work the same way - changes require negotiation (versioning).
Code Example
# API Contract as OpenAPI Specification
openapi: 3.1.0
info:
title: Order Management API
version: 2.1.0
description: |
Contract for order processing system.
**SLA Guarantees:**
- Response time: 200ms (p99)
- Availability: 99.9%
- Rate limit: 1000 requests/min per client
contact:
name: API Support
email: [email protected]
servers:
- url: https://api.example.com/v2
description: Production server
paths:
/orders:
post:
summary: Create a new order
description: |
Creates an order and returns immediately with status "pending".
Order processing happens asynchronously.
operationId: createOrder
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
responses:
'201':
description: Order created successfully
headers:
X-Request-ID:
schema:
type: string
format: uuid
description: Unique request identifier for tracking
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'400':
description: Invalid request data
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limit exceeded
headers:
X-RateLimit-Limit:
schema:
type: integer
description: Requests allowed per minute
X-RateLimit-Remaining:
schema:
type: integer
description: Requests remaining in current window
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/orders/{orderId}:
get:
summary: Get order by ID
operationId: getOrder
parameters:
- name: orderId
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Order found
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'404':
description: Order not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
CreateOrderRequest:
type: object
required:
- customerId
- items
properties:
customerId:
type: string
format: uuid
description: UUID of the customer placing the order
items:
type: array
minItems: 1
maxItems: 100
items:
type: object
required: [productId, quantity]
properties:
productId:
type: string
format: uuid
quantity:
type: integer
minimum: 1
maximum: 1000
shippingAddress:
$ref: '#/components/schemas/Address'
Order:
type: object
required:
- id
- customerId
- items
- status
- total
- createdAt
properties:
id:
type: string
format: uuid
customerId:
type: string
format: uuid
items:
type: array
items:
type: object
required: [productId, quantity, price]
properties:
productId:
type: string
format: uuid
quantity:
type: integer
price:
type: number
description: Price per unit in USD
status:
type: string
enum:
- pending
- processing
- shipped
- delivered
- cancelled
description: Current order status
total:
type: number
description: Total order amount in USD
createdAt:
type: string
format: date-time
shippingAddress:
$ref: '#/components/schemas/Address'
Address:
type: object
required:
- street
- city
- postalCode
- country
properties:
street:
type: string
maxLength: 200
city:
type: string
maxLength: 100
postalCode:
type: string
pattern: "^[0-9]{5}(-[0-9]{4})?$"
country:
type: string
pattern: "^[A-Z]{2}$"
description: ISO 3166-1 alpha-2 country code
Error:
type: object
required:
- code
- message
properties:
code:
type: string
description: Machine-readable error code
message:
type: string
description: Human-readable error message
details:
type: array
items:
type: object
properties:
field:
type: string
issue:
type: string
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- BearerAuth: []
Diagram
graph TB
subgraph "API Contract"
CONTRACT[OpenAPI Spec]
CONTRACT --> ENDPOINTS[Endpoints
Paths & Methods]
CONTRACT --> REQ[Request Schemas
Input Validation]
CONTRACT --> RESP[Response Schemas
Output Format]
CONTRACT --> AUTH[Authentication
Security Schemes]
CONTRACT --> SLA[SLA Terms
Performance, Limits]
end
subgraph "Provider Side"
IMPL[Implementation]
TESTS[Contract Tests]
IMPL -.Validates Against.-> CONTRACT
TESTS -.Enforces.-> CONTRACT
end
subgraph "Consumer Side"
SDK[Generated SDK]
MOCK[Mock Server]
CLIENT[Client Code]
CONTRACT -.Generates.-> SDK
CONTRACT -.Powers.-> MOCK
SDK --> CLIENT
end
CONTRACT --> DOCS[Auto-Generated
Documentation]
style CONTRACT fill:#90EE90
style IMPL fill:#87CEEB
style SDK fill:#FFD700
Best Practices
- Contract-first development - Define the contract before writing implementation code
- Version explicitly - Use semantic versioning in the contract (1.0.0, 2.0.0)
- Test against contract - Run contract tests ensuring implementation matches spec
- Generate SDKs from contract - Don’t hand-code clients, generate them to stay in sync
- Document SLAs in contract - Include performance guarantees, rate limits as metadata
- Use contract for mocking - Generate mock servers from contract for parallel development
- Detect breaking changes - Use tools to compare contract versions and flag breaking changes
- Make contracts discoverable - Publish contracts in a central registry or API catalog
Common Mistakes
No contract at all: Building APIs with just informal docs or README files. This leads to mismatches between documentation and implementation.
Implementation-first: Writing code first, then extracting a contract from it. This misses the main benefit - contracts enable parallel development and design discussions before coding.
Stale contracts: Writing a contract once then letting it drift as the API evolves. Contract and implementation must stay synchronized.
Incomplete contracts: Only documenting happy paths (200 responses) and ignoring error cases, edge cases, rate limits, authentication details.
Not testing against contract: Treating the contract as documentation only, not as enforceable tests. Contract tests should run in CI/CD.
Breaking changes without versioning: Modifying the contract in backward-incompatible ways without bumping major version, breaking existing clients.
Overly rigid contracts: Making contracts so specific they’re hard to evolve. Balance precision with flexibility for non-breaking additions.