API Design
API design is product and distributed-systems design expressed as a contract. Naming endpoints is the easy part. Senior-quality APIs define operation semantics, identity, concurrency, idempotency, pagination, authorization, quotas, long-running work, errors, compatibility, and observability. They remain usable during retries, partial failure, old-client coexistence, and data-model evolution.
How to use this chapter
- Read the mental model and mechanics without taking notes.
- Close the page and explain the topic aloud in five minutes.
- Work the quantitative example on paper.
- Answer the question ladder without revealing the answer signals.
- Complete one design drill and score yourself with the senior checklist.
Learning objectives
- Model resources and domain operations around user intent rather than tables.
- Design stable identifiers, idempotency, optimistic concurrency, and exactly scoped ordering.
- Implement pagination, filtering, sorting, search, bulk, and partial update safely.
- Define errors, rate limits, long-running operations, webhooks, and cancellation.
- Apply authentication, authorization, tenant isolation, privacy, and abuse controls.
- Version, deprecate, document, test, and operate APIs at senior level.
Mental model
Start from workflows and invariants. Define stable resource identity and state transitions. For each operation, specify authorization, validation, consistency, idempotency, concurrency, response boundary, and retryability. Then add ergonomics such as pagination and filtering without leaking internal storage assumptions.
Core mechanics
Resource and operation modeling
Model durable nouns such as orders, documents, uploads, and subscriptions as resources. Domain actions that do not fit field replacement can be subresources or operation endpoints, such as /orders/{id}:cancel, as long as semantics and idempotency are explicit. Avoid exposing one endpoint per database table without considering user workflow.
State machines help: document valid states, commands, guards, and terminal states. Returning the current state after a command helps clients recover from ambiguous responses. Separate command acceptance from eventual completion for long-running work.
Identifiers and references
IDs should be stable, opaque, globally or scope-unique as needed, and safe to expose. Time-sortable IDs can aid indexing but may reveal volume/time and create hot partitions if poorly randomized. Never use mutable names as the only identity.
Decide whether clients can supply IDs—for idempotent create/import—or only the server can. References should include tenant/project scope when ambiguity or authorization depends on it. Avoid allowing an ID alone to bypass parent ownership checks.
Idempotency and request identity
For retryable creation or side effects, accept a client-generated idempotency key. Scope it to principal and operation, store request fingerprint plus resulting status/response, and reject reuse with conflicting parameters. Persist the dedupe record atomically with the business transition or in a design that can reconcile gaps.
Define retention. Too short and late retries duplicate; too long and storage grows or clients cannot reuse keys. For inherently idempotent state replacement, resource version/precondition may be enough.
Concurrency and partial updates
Lost updates occur when two clients read a version and overwrite each other. Use ETag/version with If-Match, compare-and-swap, or domain commands with invariant checks. Last-write-wins is a policy, not a neutral default.
PATCH semantics must define missing versus null, nested objects, arrays, validation, and atomicity. JSON Merge Patch and JSON Patch have different behavior; custom field masks are common in RPC APIs. Return conflict information that helps clients refresh or merge.
Pagination, filtering, sorting, and search
Offset pagination is easy but can become slow and unstable under concurrent inserts/deletes. Cursor/keyset pagination encodes the last sort key and a stable tiebreaker, giving better performance and consistency. Cursors should be opaque, integrity-protected, and bound to filters/sort/tenant.
Define default/max page size, deterministic order, null ordering, filter grammar, and index-supported combinations. Total counts can be expensive or stale; make them optional or approximate where acceptable. Search results often use a separate consistency/freshness contract.
Bulk and long-running operations
Bulk APIs amortize network overhead but need limits, per-item status, atomicity definition, and retry identity. “All or nothing” across thousands of items can create locks and large transactions; partial success with item IDs is often more scalable.
Long-running operations return an operation resource containing state, progress, result/error, timestamps, and cancellation ability. Cancellation is best effort unless the underlying work supports rollback. Define expiry and whether the result remains available.
Errors, quotas, and overload
Use stable machine error codes within protocol status categories. Distinguish validation, conflict, quota, rate limit, dependency, and transient server failure. Include retry-after information only when retry is useful. Avoid exposing internals or personal data in error detail.
Rate limiting should be identity- and cost-aware: requests/s, concurrent jobs, bytes, rows, or query complexity. Return quota state or headers where useful. During overload, reject early and consistently rather than letting queues time out unpredictably.
Security and privacy
Authenticate the caller, authorize the specific action on the specific resource, and enforce tenant scope at every layer. Object-level authorization prevents insecure direct object reference. Use least-privilege scopes, short-lived credentials, audit logs, and field-level redaction.
Validate payload size, type, nested depth, URLs, file content, and callback destinations. Support data minimization, retention, export, and deletion semantics. Do not put secrets or sensitive data in URLs where logs and referrers may capture them.
Evolution, documentation, and governance
Prefer additive changes and tolerant readers. Track field/method usage before deprecation. Version incompatible behavior explicitly, but avoid cloning the entire API for every change. Contract tests, schema lint, examples, SDKs, and changelogs reduce ambiguity.
Document consistency, ordering, idempotency, pagination stability, rate limits, and error recovery—not only request fields. An API is operable when owners can trace a request, identify client/version, revoke credentials, and see quota/latency/error metrics.
Decision table
| Decision | Prefer the first option when… | Prefer the second option when… | Senior caveat |
|---|---|---|---|
| Server-generated vs client-generated ID | Central uniqueness and hidden allocation are preferred. | Offline creation, import, or idempotent create benefits from client identity. | Validate format/scope and prevent enumeration or partition hotspots. |
| Offset vs cursor pagination | Small/static datasets and arbitrary page jumps matter. | Large/mutable datasets need stable efficient continuation. | Cursor semantics must bind sort/filter and define snapshot versus live view. |
| Field update vs domain command | Simple independent attributes can be replaced safely. | Transition has invariants, side effects, or audit meaning. | Commands expose intent and idempotency but can proliferate without coherent state modeling. |
| Batch atomic vs partial success | Small batch shares one invariant and rollback is cheap. | Large independent items favor throughput and per-item retry. | Return stable item IDs/status and define ordering/duplicate behavior. |
| API version in path vs compatible evolution | A broad incompatible contract fork is unavoidable. | Additive evolution and field/method deprecation suffice. | Versioning does not remove migration/support obligations; know client usage. |
| Synchronous result vs operation resource | Work reliably completes within request deadline. | Variable/long work, queues, or external systems are involved. | Operation resource needs durable status, polling/backoff, webhook option, cancellation, and expiry. |
Quantitative reasoning
Failure modes and production signals
| Failure mode | What users see | Likely cause | Mitigation / design response | Useful signals |
|---|---|---|---|---|
| Lost update | One user silently overwrites another | No version/precondition | ETag/CAS, domain merge/conflict, return current version | Precondition failures, version conflicts, overwrite audits |
| Idempotency race | Duplicate side effect despite key | Dedupe record not atomic or concurrent first requests | Unique constraint/transaction, in-progress state, response replay | Key conflicts, duplicate business IDs, pending age |
| Cursor corruption/leak | Wrong tenant/filter results or tampering | Cursor exposes mutable/raw state without binding | Opaque signed cursor bound to principal/filter/sort | Invalid cursor rate, cross-scope checks |
| Unbounded bulk | Memory/lock saturation and long timeouts | No byte/item/transaction limits | Caps, chunking, async operation, partial status | Request bytes, items, transaction duration, queue wait |
| Breaking additive change | Old clients fail on new enum/required field | Consumer assumed closed world | Compatibility tests, unknown handling, usage telemetry, staged deprecation | Client-version errors, deserialization failures |
| Authorization gap | Cross-tenant data access | ID lookup before scope/object authorization | Central policy, tenant-qualified queries, defense in depth, audit | Denied/mismatched access, anomaly detection |
Senior-level lenses
Design ambiguous outcomes
The most important API question is often: what does the client do after timeout? Provide operation IDs, status lookup, idempotency, and immutable receipts. Do not require clients to guess whether a side effect happened.
Keep invariants near the write
Validation in a gateway is useful but not authoritative when concurrent requests race. Enforce uniqueness, balances, quotas, and state transitions transactionally in the owning service/store, then map violations to stable API errors.
Pagination is a consistency contract
Decide whether a cursor represents a snapshot or a live continuation. Snapshot pagination may require MVCC token/expiry and resources; live pagination may show duplicates/misses under mutation. Document and let clients dedupe by stable ID where appropriate.
API cost should be visible
Emit per-operation work units, rows scanned, downstream calls, bytes, and queue time. Use them for quotas, billing, and capacity. Latency alone can hide expensive requests that are fast only because the fleet is overprovisioned.
Deprecation is a product process
Publish dates and alternatives, measure client/field usage, contact owners, provide automated migration where possible, and enforce only after an agreed window. Internal APIs also need lifecycle discipline because forgotten batch jobs are real clients.
Consistency should be explicit in responses
Where useful, include resource version, event cursor, freshness timestamp, or processing state. This lets clients reason about stale views and reconciliation instead of assuming every endpoint is immediately consistent.
Interview question ladder
What makes an API idempotent?
Show strong-answer signals
Repeating the same logical operation has the same intended effect. Explain operation identity, storage of result, and difference from merely using PUT.
Why use cursor pagination?
Show strong-answer signals
Stable/efficient continuation on large mutable datasets; opaque cursor from sort key+tiebreaker. Mention limits and consistency semantics.
How should an API represent validation errors?
Show strong-answer signals
Appropriate 4xx status, stable machine code, per-field issues/path, human message, correlation, no sensitive internals.
How do ETags prevent lost updates?
Show strong-answer signals
Client reads version/ETag, sends If-Match, server atomically applies only if current version matches, otherwise 412/conflict and client refreshes/merges.
How would you design a long-running export API?
Show strong-answer signals
Create operation, 202, durable state/progress, polling with backoff and optional webhook, idempotency, cancellation, expiry, signed result URL, authorization.
What should a bulk API return on partial failure?
Show strong-answer signals
Per-item stable ID/index and status/error, overall status semantics, retry only failed items with idempotency, limits and ordering.
Design an API to transfer money between accounts.
Show strong-answer signals
Authenticated principal, idempotency key, source/destination/amount/currency, ledger transaction/invariants, operation state/receipt, concurrency, limits, timeout recovery, audit, no delete/update of ledger facts.
How do you paginate a feed while new items arrive?
Show strong-answer signals
Choose snapshot token or live keyset; stable (rank/time,id) cursor; duplicates/misses policy; client dedupe; cursor expiry; ranking changes and cache implications.
An API supports user-provided webhook URLs. What security controls are required?
Show strong-answer signals
SSRF defenses, URL scheme/port allow rules, DNS rebinding-aware resolution, block private/link-local, redirect policy, endpoint verification, signed deliveries, secret rotation, egress isolation.
How do you introduce a required field without breaking clients?
Show strong-answer signals
Server default/inference first, add optional, update SDK/docs, measure adoption, warn/deprecate old behavior, only enforce in new operation/version when safe. Consider old stored resources and retries.
Design an enterprise API platform with per-tenant custom quotas and regional residency.
Show strong-answer signals
Identity/org hierarchy, tenant-scoped routing and data plane, cost-based quotas/concurrency, residency enforcement, dedicated tiers, audit, SDK/gateway policy, online tenant migration, support tooling.
Create a compatibility policy for an API used by devices that remain offline for a year.
Show strong-answer signals
Long support horizon, additive tolerant schema, capability negotiation, min/max versions, server-side defaults, signed config, staged kill switches, replay/idempotency retention, security update path.
Design drills
Design APIs to create a multipart upload, upload parts, complete it, start virus scan/transcoding, poll status, cancel, and download. Include checksums, idempotency, versions, expiry, and signed URLs.
What the interviewer is testing
State-machine API design across large data and asynchronous work.
Design a multi-tenant search endpoint with filters, sorting, cursor pagination, facets, query cost limits, freshness metadata, and partial dependency failure.
What the interviewer is testing
Query contract, cost-aware quotas, stable continuation, and eventual consistency.
Review a hypothetical endpoint POST /updateUser that accepts all user fields and returns 200/500. Rewrite the contract for concurrency, validation, authorization, partial update, errors, and compatibility.
What the interviewer is testing
Turning a CRUD-shaped endpoint into a durable public contract.
Common weak answers and how to improve them
“Use UUIDs for scale.”
Show the stronger answer
Discuss uniqueness scope, index locality, exposure, client generation, partitioning, and alternatives such as time-sortable/randomized IDs.
“Version the API whenever something changes.”
Show the stronger answer
Prefer compatible additive evolution; version only incompatible semantics and manage migration.
“Return all records and let clients filter.”
Show the stronger answer
Address bandwidth, privacy, latency, indexes, quotas, and stable server-side filtering/pagination.
“Retry 500s.”
Show the stronger answer
Use deadline, idempotency, specific transient errors, Retry-After, jitter, and avoid retrying overload blindly.
“Authorization is handled by the gateway.”
Show the stronger answer
The owning service must enforce object/tenant authorization and invariants because gateways can be bypassed or race with state changes.