API Design Cheat Sheet
Need X → use Y. Each row opens the lesson that explains the consumer need, the guarantee, the failure mode and the trade-off behind the shortcut.
Contract shape
An operation that isn't CRUD on a noun→Model the action as a resource: POST /orders/{id}/cancellationsAn entity with a lifecycle (draft → active → closed)→Explicit state machine; invalid transitions rejected with 409One screen needs data from five services→A backend-for-frontend that owns the screen's contractConsumers need 3 of your 80 fields→Purpose-built response models, not serialized database rowsDistinguishing "field not sent" from "set to null"→Explicit absent-vs-null semantics in the request contractThe same concept named three ways across endpoints→One vocabulary: casing, id formats, timestamps everywhereA read that must never change anything→GET: safe, cacheable, side-effect-free by contractFull replace vs partial modification→PUT to replace; PATCH with defined merge semantics
Errors
Clients string-matching error messages→Stable machine-readable `code` plus human `message`One catch-all 400 for every failure→A taxonomy clients can branch on: validation, authz, conflict, rate limitA form submit with 12 invalid fields→Field-level validation errors in a single responseClients can't tell "retry" from "give up"→Explicit retryability: status semantics plus Retry-AfterA batch where 3 of 5 items succeed→Per-item results with explicit partial-failure semantics
Lists & queries
A list that can grow unbounded→Cursor pagination with a stable sort keyA small admin table that wants page numbers→Offset pagination, with the drift accepted and documentedDuplicates or gaps while paging a live feed→Keyset continuation instead of offsets under concurrent writesPages that reshuffle between requests→Stable ordering with a unique tiebreaker columnArbitrary client-invented filter combinations→A filter allowlist with typed, index-backed parametersRelevance queries like q=running shoes→A separate search contract, not more filter params
Reliability
A create the client may retry→Idempotency-Key with stored result replayA timeout that may or may not have charged→Retry the same key; the server replays the stored outcomeTwo writers overwriting each other's changes→Version or ETag with If-Match; conflict returns 409/412POST succeeds, immediate GET returns 404→Documented read-after-write consistency expectationsOne request spanning payment, inventory and shipping→A state machine or workflow — never a distributed transactionA thousand clients retrying in lockstep→Exponential backoff plus jitter as documented contract guidance
Real-time & async
A request that takes 15 minutes→202 Accepted plus a job resource the client pollsJob states clients can build UI on→queued/running/succeeded/failed with result retrieval and retentionTelling the client a job finished→Webhook or SSE for push; polling as the documented fallbackServer-to-browser one-way events→SSE with Last-Event-ID resume on reconnectBidirectional messages needing acks and ordering→A WebSocket message contract with types, schemas and sequence numbersA consumer reading slower than you send→Backpressure policy: bounded buffers and a documented disconnect rule
Webhooks
Notifying third parties about events→Signed webhooks with event ids, delivery ids and timestampsA consumer endpoint down for an hour→A retry schedule with dead-lettering and manual redriveThe same event delivered twice→Consumer-side event_id deduplication before processingEvents arriving out of business order→Sequence numbers, versions, or fetch-state-on-event
Security boundary
Server-to-server caller identification→Scoped API keys with rotation and per-key rate limitsA token that can do everything→Granular scopes: projects:read, billing:write — not one admin scopeDeciding who may DELETE /projects/{id}→Resource-level authorization stated in the contract, not impliedOne caller flooding everyone else→429 with Retry-After and documented limit headersBurst control vs monthly entitlements→Rate limits and quotas as separate, separately communicated clauses
Evolution
Adding a field to a response→Additive change — no version bump, old clients ignore itA new enum value old clients will receive→Documented unknown-variant handling as client guidanceRemoving a field without an outage→Introduce replacement → measure usage → deprecate → removeA breaking change you cannot avoid→New version, dual support, a migration window with a deadlineKnowing whether anyone still uses field X→Per-consumer, per-field usage telemetry before removal
Operations
Tracing one request across gateway and services→A request id propagated everywhere and returned in error bodiesA 5MB response feeding a mobile list screen→Trim the payload: field selection, pagination, purpose-built models200KB JSON over slow links→Compression above a size threshold; skip tiny and already-compressed bodiesKnowing each endpoint is healthy→Rate, errors, duration and size metrics — without high-cardinality labelsProvider and consumer disagree about the contract→Contract tests in CI: consumer expectations verified against the provider