API-first does not mean "we have an API." It means the API is the primary product interface, and your UI is just one consumer. Stripe is the canonical example: their dashboard does nothing that a third party cannot do via the API. This forces good design because the API is held to public-product standards from day one.
Building API-first adds upfront cost. It pays back when you need integrations, partners, an SDK, or a mobile app that consumes the same backend. Building API-second means retrofitting your API to expose internals that were never meant to be public, which is painful and produces a worse API.
OpenAPI Spec Before Code
Write the OpenAPI (formerly Swagger) spec for your endpoint before writing implementation code. This forces you to think about the public contract, naming, and shape before the implementation locks you in.
What the spec captures
- Endpoint paths and HTTP methods
- Request body shape (required vs optional fields, types, validation rules)
- Response shape per status code (200, 201, 400, 401, 404, 500)
- Authentication requirements
- Rate limit headers
- Examples for every endpoint
What spec-first prevents
- Inconsistent field naming (createdAt in one endpoint, created_at in another)
- Inconsistent error shapes (some endpoints return {error: "string"}, others {errors: ["array"]})
- Missing edge cases (what happens with empty body? With invalid JSON? With unauthorized?)
- Breaking changes shipped accidentally
Tools
Stoplight, Postman, or hand-write the YAML. Generate client SDKs from the spec using openapi-generator. Some teams generate the server stub too, but pure server generation tends to produce ugly code.
Contract Testing
Beyond unit and integration tests, contract tests verify that your API matches its published spec. If someone changes the API in a way that breaks the contract, CI catches it before deploy.
Pact (consumer-driven contract testing)
Consumer writes a contract: "I send X, I expect to receive Y." Provider runs the contract against its actual API. If the response shape changes, the provider test fails — the consumer has not even updated.
Pact is great for multi-team or multi-service architectures. For solo or small teams, simpler approaches (validating responses against the OpenAPI spec at test time) are often sufficient.
The minimum viable contract test
For each endpoint, a test that hits it with a known input and validates the response shape matches the OpenAPI definition. Easy to add to any CI pipeline. Catches 80% of contract drift.
Versioning Strategy
You will need to change the API. Versioning gives you a path to evolve without breaking existing clients.
URI versioning
/v1/users, /v2/users. The version is in the URL path. Pros: explicit, easy for developers, easy to route. Cons: requires clients to update URLs to migrate.
Stripe, GitHub, Slack all use URI versioning for their public APIs. The pattern is mature and works.
Header versioning
Accept-Version: 2.0 header. Same URL, different version based on header. Pros: URL stays clean, easier to add deprecation warnings. Cons: harder to test in browser, less discoverable.
GitHub uses date-based header versioning (Accept: application/vnd.github.2022-11-28+json) for advanced versioning. Powerful but complex.
The migration policy
Publish the policy upfront. Typical: "We maintain v1 for at least 18 months after v2 is GA. Deprecation warnings 6 months before sunset. End-of-life with 60-day final notice."
Without a published policy, every API change becomes a customer drama. With one, developers know what to expect.
Authentication
API keys for server-to-server
Long random string in the Authorization header: Bearer sk_live_xxxxxx. Developer manages the key.
- Scoped keys (read-only vs read-write) — common after v1.
- Rotatable without downtime — keys can be paired with a "previous key" grace period.
- Webhook signing for inbound webhook verification.
OAuth for user-delegated access
When a third-party app needs to access user data on the user's behalf. Used by Slack apps, GitHub apps, anything in an app marketplace.
OAuth 2.0 with PKCE for SPAs and mobile. OAuth 2.0 with client credentials for server-to-server when you want OAuth flow.
Both at the same time
Most B2B APIs need both. Direct integrations (one customer's backend talking to yours) use API keys. Marketplace apps use OAuth so end users can install third-party apps on their workspace.
Rate Limiting
Rate limits protect your service and create a pricing lever. Set them deliberately.
Per-key vs per-endpoint
- Per-key: overall request budget. Typical: 60 req/min on free tier, 600 on paid, 6000 on enterprise.
- Per-endpoint: additional limits on expensive operations. Search endpoint might be 10 req/min even on enterprise. List endpoints might be 30 req/min.
Rate limit headers
Return them on every response so clients can self-throttle:
- X-RateLimit-Limit: 600
- X-RateLimit-Remaining: 543
- X-RateLimit-Reset: 1717862400 (unix timestamp)
When the client exceeds the limit, return 429 Too Many Requests with a Retry-After header.
Webhook Events
For events that should notify the client (resource updated, async job completed, payment processed), webhooks beat polling.
Webhook design
- HMAC signature in header so receivers can verify authenticity (Stripe-style).
- Retries with exponential backoff when receiver returns non-2xx. At least 5 retries over 24 hours.
- Idempotency: include a unique event ID so receivers can dedupe.
- Dashboard for delivery logs so customers can debug their webhook handlers.
Event list
Publish the full list of webhook events with payloads. Like a public schema. Clients need to know what to subscribe to and what to expect.
Pagination
List endpoints must paginate. The question is how.
Cursor-based pagination
Recommended for most APIs. Client passes a cursor token, server returns next page + next cursor. Stable under inserts/deletes during pagination.
Stripe, GitHub, Linear all use cursor-based pagination. The standard.
Offset pagination
Page numbers (page=2, per_page=20). Easy to understand, breaks under concurrent writes. Use only for static or rarely-changing data.
Error Responses
Inconsistent error shapes destroy developer experience. Pick one shape and use it everywhere.
Recommended shape
{
"error": {
"code": "invalid_request",
"message": "The 'email' field is required.",
"field": "email",
"request_id": "req_abc123"
}
}
- code: machine-readable error type for client logic
- message: human-readable for logging and display
- field: which field failed validation, if applicable
- request_id: for support — customer reports this, you find the request
The API-First Checklist
| Item | Required for v1? | Pre-launch? |
|---|---|---|
| OpenAPI spec | Yes | Yes |
| API key authentication | Yes | Yes |
| Consistent error shape | Yes | Yes |
| Versioning in URI | Yes | Yes |
| Rate limiting | Yes (basic) | Yes |
| Pagination | Yes | Yes |
| Contract tests | Yes | Yes |
| Webhooks | No | v2 typically |
| OAuth flow | No | When marketplace launches |
| SDK in 3+ languages | No | v2 typically |
| Deprecation policy doc | Yes | Yes |
The Documentation Standard
Bad docs kill API adoption faster than bad design. The minimum standard:
- Quickstart: get a working request in 5 minutes.
- Every endpoint: URL, method, parameters, response shape, example request + response.
- Code samples in curl, Python, JavaScript minimum. Test them in CI.
- Authentication guide: how to get a key, how to use it, how to rotate.
- Errors reference: every error code, what causes it, how to fix.
- Changelog: every API change with date and impact.
- Searchable docs: Algolia DocSearch or equivalent.
The Mistakes I See Most
- Inconsistent naming: camelCase in one endpoint, snake_case in another. Pick one, enforce in CI.
- Inconsistent error shapes: some endpoints return {error: "..."}, others {errors: [...]}, others {message: "..."}. Pick one, enforce in CI.
- Missing pagination: "list all users" returns 50K records, kills client. Always paginate from v1.
- No rate limiting: one client takes down your service. Add basic limits from day one.
- No deprecation policy: every change becomes a fire. Publish the policy before you need it.
- Manual SDK generation: hand-written SDKs in 5 languages drift from the API. Generate from OpenAPI.
When NOT to Go API-First
API-first adds cost. Skip it if:
- You are building a consumer app with no integration story (Instagram, TikTok do not need API-first).
- You are pre-PMF and need to ship fast. Add API-first discipline after PMF.
- The product is an internal tool with one consumer (your own UI).
For B2B SaaS targeting developers, fintech, infrastructure, automation — API-first is non-negotiable. Skip it and you cannot ship integrations, mobile, or partner apps without painful retrofitting.
API design review
If you are designing a new API or want a second opinion on your existing one, I do 60-minute API reviews. We check the spec, error shapes, versioning, and developer experience.
Book a discovery call