Sign in
TodayMapLearnPracticeReview
Library
12 MINcoreInterview PreparationNot started

API Design

Good API design is the difference between a system developers love and one they tolerate. The principles: RESTful resource modeling, clear versioning, sensible pagination, consistent error handling, and idempotency for safety. A good API is consistent (same conventions everywhere), predictable (you can guess endpoints), and forgiving (idempotent so retries are safe). A bad API is the opposite — surprising, inconsistent, and dangerous to retry.

Why this matters

APIs are contracts: once published, breaking changes hurt every consumer. Bad APIs cause integration bugs, support tickets, and rewrites. The 2017 Stripe API is praised because it's consistent and well-versioned; many internal APIs are reviled because they're not. API design is also a top interview topic — every system design interview touches it. A candidate who can articulate REST principles, when to version, how to paginate, and why idempotency matters stands out from one who can only write endpoints. Good API design is half engineering, half empathy for the consumer.

Prerequisites
  • REST — Representational State Transfer
  • HTTP — HyperText Transfer Protocol
  • Idempotent Operations
Related
  • REST — Representational State Transfer
  • GraphQL — Query Language for APIs
  • RPC — Remote Procedure Call
  • Idempotent Operations
  • Gatekeeper Pattern
Used in

Foundational.

Lesson

How it works

Good API design rests on five pillars:

  1. Resource modeling: model the API around nouns (resources), not verbs (actions). /users, /orders, /orders/{id}/items — not /createOrder or /getUserData.
  2. Versioning: APIs change. Version them from day one (/v1/...) so you can introduce breaking changes without breaking existing clients.
  3. Pagination: list endpoints must paginate. Returning 100k records in one response is broken.
  4. Error handling: consistent, structured error responses with meaningful codes and messages.
  5. Idempotency: write operations should be safe to retry. POST without idempotency is a bug waiting to happen.

REST (Representational State Transfer) is the dominant style. It maps HTTP verbs to actions: GET (read), POST (create), PUT/PATCH (update), DELETE (delete). The URL identifies the resource; the verb identifies the action.

API versioning is non-negotiable for public APIs. Three approaches:

  • URI versioning (/v1/users): the most common and the most explicit. Clients see the version in every request; routing is trivial. Downside: version in URL is "ugly" to some and breaks the principle that URLs are resource identifiers.
  • Header versioning (Accept: application/vnd.api+json; version=1): cleaner URLs, version is a content negotiation. Downside: invisible in logs and tests; harder to route.
  • Query parameter (/users?version=1): easy but mixes version with filtering. Generally discouraged.

The Stripe / GitHub / Twitter pattern: URI versioning. The cost of "ugly" URLs is far less than the cost of invisible versions. Most teams pick URI versioning and never look back.

When to bump the version: any breaking change. New endpoints, new fields, new optional parameters — no version bump needed. Removing or renaming fields, changing types, changing semantics — version bump. Maintain old versions for a deprecation window (6-24 months) so clients can migrate.

List endpoints must paginate. Three styles:

  • Offset/limit (?offset=100&limit=20): the simplest. SQL-friendly. Problem: skipping is O(N) in many databases, and items shift if data changes between pages.
  • Cursor-based (?cursor=abc123&limit=20): the cursor encodes the position (typically the last seen ID + sort key). Stable across inserts/deletes; O(1) skip in databases. The right choice for most APIs.
  • Page-based (?page=5&per_page=20): user-friendly for UI but has the same problems as offset. Use only for low-volume, stable data.

Return pagination metadata in the response: next_cursor, has_more, total (if cheap to compute). Don't make clients guess whether there's another page.

Rate-limit list endpoints to reasonable page sizes (e.g., max 100 per page). A ?limit=1000000 request is a denial-of-service vector.

Errors must be structured and consistent

Bad: 500 Internal Server Error with body "something went wrong." Good: a structured response with a stable error code, a human-readable message, and a request ID for support. Use HTTP status codes correctly (200 success, 4xx client error, 5xx server error). Don't return 200 with an error body — that breaks every HTTP client and framework. Include a request ID in every response so support can correlate. Stripe's error format is the gold standard: {type, code, message, param, request_id}. Pick a format and apply it everywhere — inconsistency is the #1 API complaint.

Idempotency makes APIs safe to retry — critical because networks fail and clients retry. HTTP spec defines GET, PUT, DELETE as idempotent; POST is not. But many POSTs should be idempotent (creating a charge, sending a message).

The fix is the Idempotency-Key header (Stripe's pattern, now widely adopted):

  • Client generates a UUID per logical operation.
  • Sends Idempotency-Key: <uuid> on the POST.
  • Server stores the key + response for 24-48 hours.
  • If the same key is sent again, server returns the stored response (not a new operation).

This makes POST safe to retry. Without it, a network timeout after the server created the charge but before the client received the response means a retry creates a duplicate. With it, the retry returns the original charge.

For non-idempotent operations (truly unique actions like "spin a slot machine"), don't accept an idempotency key — but document this clearly so clients know not to retry.

Check yourself
interview

A client calls `POST /v1/charges` to charge $100. The server charges the card but the response is lost due to a network timeout. The client retries. What happens without idempotency, and how does an idempotency key fix it?

Pick one answer.

Check yourself
core

Which is the better RESTful design for an API that lists a user's orders?

Pick one answer.

Check yourself
advanced

Why is cursor-based pagination better than offset/limit for a list endpoint that receives frequent inserts?

Pick one answer.

Engineering mental model

Mental model. Think of API Design as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does API Design mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”

Design lens

Before choosing API Design, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.

Original NO CAP systems visual for API Design.
Image unavailable. Original NO CAP systems visual for API Design.
API Design: a compact system-thinking visual.— Original NO CAP visual.
curl -i https://api.example.com/v1/api-design

# Look for:
# - status code
# - latency
# - retryability
# - response size
A minimal engineering sketch for reasoning about API Design.

Back-of-the-envelope reasoning

Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.

Interactive sandboxdeterministic

Interactive thought experiment: API Design

Change the variables below and predict what breaks first in API Design. The production lab can later reuse these same inputs.

System pressure6%
Try this

Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.

Hint

If you are stuck on API Design, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.

Check yourself
solid

You increase traffic by 10× in a system using API Design. What should you inspect first?

Pick one answer.

Check yourself
interview

Which statement is the safest engineering habit when using API Design?

Pick one answer.

Try this
interview

You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.

Production scenario: your system uses API Design, traffic suddenly spikes, and p99 latency doubles. What is your first move?

Interview drill

Answer this without notes: When would you choose API Design, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.

Engineering lens

A useful engineering lens for API Design: define the problem it solves, the simpler design that fails first, the constraint that forces you to introduce this concept, and the new failure modes the concept creates.

Numerical sanity check

Back-of-the-envelope reasoning beats fake precision. State your traffic, payload, concurrency and growth assumptions explicitly, then calculate enough to know whether the current architecture is orders of magnitude away from the target.

Check yourself
interview

Do not optimize for a memorized definition. Reason from the workload and failure mode.

Imagine the simplest version of a system using API Design. What breaks first as traffic grows by 10×, and what would you change before reaching 100×?

Pick one answer.

Trade-offs

What you gain, what you pay

Pros
  • +Consistent, predictable APIs are easy to integrate and maintain.
  • +Versioning lets APIs evolve without breaking clients.
  • +Idempotency makes APIs safe to retry — essential for reliability.
  • +Structured errors make debugging fast.
Cons
  • −REST doesn't fit every domain (complex operations need RPC or GraphQL).
  • −Versioning adds operational complexity (multiple versions to support).
  • −Cursor pagination is harder for clients than offset/limit.
  • −Idempotency requires server-side state and key management.
Failure modes

How this breaks in production

  • Non-idempotent POSTs that double-execute on retry.
  • No versioning — breaking changes break every client.
  • Returning 200 with error bodies — breaks HTTP clients and frameworks.
  • Unpaginated list endpoints that return 100k records in one response.
Common mistakes

Don't fall into these traps

  • •Using verbs in URLs (RPC style) instead of resources (REST style).
  • •Not versioning from day one — retroactive versioning is painful.
  • •Returning 200 with an error body instead of the correct 4xx/5xx status.
  • •Forgetting idempotency on POST — leading to duplicate charges/sends on retry.
Where you see it

Real systems using this

Stripe API: the gold standard — versioned, idempotent, well-documented.GitHub API: RESTful with consistent pagination and error formats.Every well-designed internal microservice API.
Teardowns

How real systems implement this

  • Stripe API — The reference for good API design: URI versioning (`/v1/charges`), Idempotency-Key header for safe retries, structured errors with request IDs, cursor-based pagination. Their docs are also a model — every endpoint has examples and edge cases.
  • GitHub REST API — RESTful resource modeling (`/users/{user}/repos`), consistent pagination via Link headers, ETag-based caching. The pattern most public APIs now follow.
Interview prompts

Practice saying it out loud

  • Q1Design a RESTful API for an e-commerce system. What endpoints, verbs, and resources?
  • Q2How do you make POST requests safe to retry?
  • Q3Compare offset/limit and cursor pagination. When would you use each?
  • Q4When should you bump the API version?
Research

Further reading & references

System Design Primer
Open source
ByteByteGo — Scale from zero to millions
ByteByteGo
System Design Tutorial
GeeksforGeeks
System Design Roadmap
roadmap.sh
Interview Preparation reference
Reference
Interview Preparation reference
Reference
Interview Preparation reference
Reference
ByteByteGo — Scaling Websites
ByteByteGo

Core explanations are original NO CAP material. External references are provided for deeper study and standards.

What next?

Mark as understood once the mental model clicks.

Next recommended

REST — Representational State Transfer