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.
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" }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
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}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"}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}}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
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
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.
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.
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.