API Playbook
Presentation

The 10 API Commandments

The fundamental principles that separate APIs people love from APIs people work around. Each one links to deeper content for self-exploration.

1

Your API Is a Contract, Not an Implementation Detail

Every field, endpoint, and status code is a promise. Treat it like one.

Once a consumer depends on a field, renaming it breaks their app. Once they parse your error format, changing it breaks their error handling. Your API is a published interface with downstream dependencies — not internal code you can refactor freely.

Do this

Write the spec before the code. Review changes for backward compatibility.

Not this

Rename fields for consistency without a migration plan. Ship and "fix later."

Bad

// Renamed without warning — mobile app crashes
{ "user_name": "alice" }

Good

// Old field kept, new field added
{ "userName": "alice", "user_name": "alice" }
2

Design for Your Consumer, Not Your Database

Resources are business concepts, not table names. Verbs are HTTP methods, not URL segments.

Your API serves developers, not your ORM. When your API mirrors your database schema, every schema migration becomes an API breaking change. Model around what consumers need, not how you store it.

Do this

Use plural nouns (/orders, /users). Express actions through HTTP methods.

Not this

POST /sendEmail, GET /getUser, /order_line_items.

Bad

POST /api/sendEmail
POST /api/createUser
GET /api/getOrderById?id=123

Good

POST /api/emails
POST /api/users
GET /api/orders/123
3

Use HTTP Status Codes. For Real.

200 means success. Stop returning 200 with an error body.

Every HTTP client, monitoring tool, retry logic, and cache relies on status codes. When you return 200 for errors, you break all of them. Consumers have to parse your body to know if the request succeeded — that's a bug, not a design choice.

Do this

400 for bad input, 401 for unauthenticated, 403 for forbidden, 404 for not found, 422 for validation, 429 for rate limited, 500 for server errors. Use RFC 9457 Problem Details for error bodies.

Not this

200 with {"error": "not found"}. 500 for everything that isn't a 200.

Bad

HTTP/1.1 200 OK

{"status": "error", "message": "User not found"}

Good

HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{"type": "/errors/not-found", "title": "Not Found", "status": 404}
4

Be Consistent. Obsessively.

Pick one naming convention, one date format, one envelope structure — and enforce it everywhere.

Inconsistency is the #1 source of developer frustration. When userId is camelCase in one endpoint and user_id in another, every consumer has to handle both. Automate consistency with linting so you don't argue about it in code reviews.

Do this

One casing style (camelCase or snake_case). ISO 8601 dates. {data: ...} envelope. Enforce with Spectral.

Not this

userId here, user_id there. Unix timestamps in one endpoint, "April 13" in another.

Bad

{"userId": "u_1", "created_at": 1681380000, "OrderStatus": "active"}

Good

{"user_id": "u_1", "created_at": "2026-04-13T10:00:00Z", "order_status": "active"}
5

Paginate Everything. From Day One.

Every list endpoint will eventually return too many items. Add cursor pagination before it's too late.

Adding pagination later is a breaking change. Offset pagination breaks when items are inserted and gets slower at scale. Cursor pagination is stable, O(1), and works from the start. Return _links so the frontend never builds pagination URLs.

Do this

Cursor pagination by default. Return _links with next/prev. Let the backend own the pagination logic.

Not this

No pagination ('we only have 50 items'). Offset pagination as the default.

Bad

{"data": ["...all 12,000 items..."]}

Good

{"data": ["...20 items..."], "_links": {"next": {"href": "/api/tasks?page[after]=abc123"}}, "meta": {"has_next_page": true}}
6

Every Write Needs an Idempotency Strategy

Without idempotency, every retry is a gamble. Double charges, duplicate orders, duplicated everything.

Networks fail. Clients retry. If your POST endpoint creates a new resource every time it's called, a single network timeout can cause a double charge. Idempotency keys let the server recognize duplicate requests and return the cached result instead of re-processing.

Do this

Require an Idempotency-Key header on POST endpoints. Store and return cached results for duplicate keys.

Not this

Hope that retries won't happen. Tell clients to 'just don't retry.'

Bad

POST /api/payments
// Client retries after timeout -> two charges

Good

POST /api/payments
Idempotency-Key: pay_req_abc123
// Server returns cached result on retry -> one charge
7

Secure the Defaults, Not Just the Happy Path

API keys are identification, not authentication. Rate-limit your auth endpoints. Never put secrets in URLs.

The OWASP API Top 10 exists because most APIs ship with the same vulnerabilities: broken object-level authorization, excessive data exposure, missing rate limiting on login endpoints. Your API key identifies the caller — it doesn't prove who they are. Secrets in URLs end up in server logs, browser history, and CDN caches.

Do this

OAuth2 for authentication. Rate-limit login endpoints aggressively. Transmit secrets in headers, never URLs. Return only the fields consumers need.

Not this

API keys as auth. No rate limiting on /login. Secrets in query parameters.

Bad

GET /api/users/123?api_key=sk_live_REDACTED

Good

GET /api/users/123
Authorization: Bearer eyJhbG...
X-API-Key: pk_live_app_id
8

Measure What Matters: p99, Not Averages

Your average latency is a lie. Your most active users live at p99.

99 requests at 50ms + 1 request at 5000ms = average of 99ms. Looks fine. But that p99 user — who hits your API 100 times per session — has a 63% chance of experiencing the 5-second response. Averages hide your worst performance from your best customers.

Do this

Track p50, p95, p99. Set SLOs based on percentiles. Alert on error budgets, not individual failures.

Not this

Monitor average latency. Set uptime target to 100%. Alert on every 500.

9

Documentation Is Product, Not Afterthought

Time-to-first-successful-call is the only metric that matters for your docs.

Stripe didn't win because they had the best payment API. They won because they had the best documentation. If a developer can't get a 200 response in 5 minutes with your docs, they'll find an API where they can. Write the spec first (OpenAPI), include curl examples for every endpoint, show error responses — not just the happy path.

Do this

Spec-first with OpenAPI. Curl + JSON examples for every endpoint. Document errors, not just successes. Measure time-to-first-call.

Not this

Auto-generated docs from code comments. 'Refer to the source code.' Happy-path-only documentation.

10

Design for Evolution, Not for Versioning

The best versioning strategy is not needing one. Design for additive change.

Every API version you maintain is a version you support, document, test, and eventually sunset. If you design for evolution — additive fields, optional parameters, Postel's Law — you'll version once every few years instead of every release. When you must deprecate, use Sunset headers and migration guides, not surprise removals.

Do this

Add fields, never remove them. Deprecate with Sunset headers and timelines. Apply Postel's Law: be liberal in what you accept, conservative in what you send.

Not this

URL versioning for every change (/v1, /v2, /v3). Removing fields without notice. Breaking changes on Friday.

Go deeper

Each commandment links to detailed topics with JSON examples, diagrams, checklists, and do/don't guidance.

Built by Noosia Digital — learning experiences that stick.