API-First Design
Design the API contract before writing a single line of implementation. The principle behind Stripe, GitHub, and Twilio.
API-First Design
Design the API contract before writing a single line of implementation. The principle behind Stripe, GitHub, and Twilio.
What you'll learn
- API-first: design the contract (OpenAPI/protobuf/GraphQL schema) before writing implementation. It is a product decision, not a technical detail.
- API-first enables parallel development — consumers mock the spec while the backend implements it.
- Contract tests in CI ensure the implementation never drifts from the published spec.
- Breaking changes (field removed/renamed, type changed) require a version bump. Additive changes (new optional field) are non-breaking.
- Stripe's date-pinned versioning model is the gold standard: old clients never break, new clients get the latest API.
Lesson outline
The $40 billion API company story
In 2010, a 22-year-old developer named Patrick Collison was frustrated with Stripe's competition. Payment APIs were terrible — weeks of approval, mountains of documentation, cryptic XML, and constant integration failures.
Stripe's first decision was unusual: before writing any implementation, they designed the API. They wrote example code showing how a developer would integrate Stripe. The API had to be so intuitive that the integration docs could fit on a single page.
That API-first decision — design the developer experience before building the system — is a significant part of why Stripe became a $95 billion company.
What is API-first design?
API-first means the API contract (schema, endpoints, request/response shapes, error codes) is designed, reviewed, and agreed upon BEFORE any backend implementation begins. The API is treated as a product — not an implementation detail.
Implementation-first vs API-first: the difference in outcomes
| Scenario | Implementation-first | API-first |
|---|---|---|
| Mobile and backend teams work in parallel | Mobile blocks waiting for backend to finish | Mobile mocks the API spec and builds; both finish simultaneously |
| API field naming | user_id int (whatever the DB schema uses) | Discussed, agreed as uuid string (matches consumer expectations) |
| Breaking API changes | Discovered in integration testing after weeks of work | Contract tests catch violations in CI before they are merged |
| External developer experience | Inconsistent, undocumented, changes break clients | Versioned, documented, SDK auto-generated from spec |
| Onboarding new team members | Read the source code to understand what the API does | Read the OpenAPI spec — the spec IS the documentation |
The API-first workflow in practice
How to do API-first design
01
Write the API spec first — OpenAPI (REST), protobuf (gRPC), or GraphQL schema. Define endpoints, request/response shapes, error codes, and pagination.
02
Review the spec with all consumers — mobile team, frontend team, third-party partners. Negotiate field names, response shapes, and error handling before any code is written.
03
Commit the spec to source control — version it like code. Treat spec changes as PRs requiring review.
04
Generate mocks from the spec — tools like Prism or Mockoon serve the spec as a real HTTP server. Consumers start building immediately.
05
Implement against the spec — the backend treats the spec as the acceptance test. Every endpoint must match the contract exactly.
06
Add contract tests to CI — tools like Dredd or Schemathesis verify that the running service still matches the published spec on every commit. Breaking changes fail the build.
Write the API spec first — OpenAPI (REST), protobuf (gRPC), or GraphQL schema. Define endpoints, request/response shapes, error codes, and pagination.
Review the spec with all consumers — mobile team, frontend team, third-party partners. Negotiate field names, response shapes, and error handling before any code is written.
Commit the spec to source control — version it like code. Treat spec changes as PRs requiring review.
Generate mocks from the spec — tools like Prism or Mockoon serve the spec as a real HTTP server. Consumers start building immediately.
Implement against the spec — the backend treats the spec as the acceptance test. Every endpoint must match the contract exactly.
Add contract tests to CI — tools like Dredd or Schemathesis verify that the running service still matches the published spec on every commit. Breaking changes fail the build.
A minimal OpenAPI spec example
openapi: 3.0.0 info: title: Payment API version: 1.0.0 paths: /payments: post: summary: Create a payment requestBody: required: true content: application/json: schema: type: object required: [amount, currency, source] properties: amount: type: integer description: Amount in smallest currency unit (cents) currency: type: string enum: [usd, eur, gbp] source: type: string description: Payment method token responses: '201': description: Payment created successfully '422': description: Validation error
1// Dredd contract test — fails CI if implementation breaks the spec2import Dredd from 'dredd';34const dredd = new Dredd({The OpenAPI spec is checked into source control and is the acceptance test5path: ['./api/openapi.yaml'], // the spec is the source of truth6endpoint: 'http://localhost:3000',7reporter: 'dot',8});910dredd.run((err, stats) => {11if (stats.failures > 0 || stats.errors > 0) {Fails the CI build if any endpoint response does not match the spec12console.error('Contract test FAILED — implementation does not match spec');13process.exit(1); // fails CI build14}15console.log('All contract tests passed');16});
API versioning: the contract you make with consumers
Once an API is published, consumers depend on it. Breaking that contract — removing a field, changing a type, renaming an endpoint — breaks consumers. API-first design forces you to think about versioning from day one.
Versioning strategies
- URL versioning: /v1/payments, /v2/payments — Simple and explicit. Clients know exactly which version they are using. Works well for major breaking changes. Downside: code duplication when versions are mostly similar.
- Header versioning: Accept: application/vnd.api+json;version=2 — Cleaner URLs. Harder to test in a browser. Used by GitHub API.
- Date-based versioning: Stripe-Version: 2024-01-01 — Stripe's approach. Every breaking change creates a new version date. Clients pin to the version they tested against. New clients get the latest. Old clients never break.
- Additive-only (non-breaking) changes — Adding fields to responses, adding optional request fields, adding new endpoints — these do not require new versions. Semantic versioning rule: only remove or rename fields when bumping the major version.
The Stripe versioning model is the gold standard
Stripe has APIs from 2013 that still work. They pin every API consumer to the version date they first integrated with. When Stripe changes the API, old integrations never break. The cost: Stripe maintains a version translation layer. The benefit: zero client churn, legendary developer experience.
Which type of API change is "non-breaking" and does not require a version bump?
REST vs gRPC vs GraphQL: choosing the right contract format
| Protocol | Best for | Contract format | Code generation |
|---|---|---|---|
| REST / OpenAPI | Public APIs, web/mobile clients, simple CRUD | OpenAPI YAML/JSON | Auto-generate SDKs, docs, mocks |
| gRPC / Protobuf | Internal microservice communication, streaming, high performance | .proto files | Auto-generate client/server code in any language |
| GraphQL | Flexible client-driven data fetching, BFF pattern, complex nested data | SDL schema | Type-safe clients, auto-generated resolvers |
| AsyncAPI | Event-driven systems, message queues, Kafka | AsyncAPI YAML | Auto-generate producers/consumers |
The API-first principle applies to all of them. The protocol changes; the principle does not: design the contract, get alignment, then implement.
How this might come up in interviews
Backend and platform engineering interviews — often asked when discussing distributed systems, microservices, or developer experience design.
Common questions:
- What is API-first design and why does it matter?
- What is an OpenAPI spec and how do you use it?
- How do you prevent breaking API changes from reaching consumers?
- When would you choose gRPC over REST?
- How does Stripe handle API versioning?
Key takeaways
- API-first: design the contract (OpenAPI/protobuf/GraphQL schema) before writing implementation. It is a product decision, not a technical detail.
- API-first enables parallel development — consumers mock the spec while the backend implements it.
- Contract tests in CI ensure the implementation never drifts from the published spec.
- Breaking changes (field removed/renamed, type changed) require a version bump. Additive changes (new optional field) are non-breaking.
- Stripe's date-pinned versioning model is the gold standard: old clients never break, new clients get the latest API.
Before you move on: can you answer these?
What is the deliverable of API-first design?
A machine-readable contract: an OpenAPI spec, a .proto file (gRPC), or a GraphQL schema — checked into source control and agreed upon by all consumers before implementation begins.
Why do contract tests matter in CI/CD?
They automatically verify that the running implementation still matches the published spec on every commit. They catch breaking changes before they reach consumers.
Ready to see how this works in the cloud?
Switch to Career Paths for structured paths (e.g. Developer, DevOps) and provider-specific lessons.
View role-based pathsSign in to track your progress and mark lessons complete.
Discussion
Questions? Discuss in the community or start a thread below.
Join DiscordIn-app Q&A
Sign in to start or join a thread.