Fundamentals
API design
An API is a contract that outlives the code behind it. Design the styles, methods, pagination, errors and async flows so changes stay additive.
An API is a promise. Once another team, a mobile app or a paying customer depends on it, you can change the code behind it whenever you like. Changing the contract itself is slow, political and sometimes impossible. Mobile apps stay installed for years. Customers write an integration once and never touch it again. The implementation is temporary. The interface is close to permanent.
That makes API design a core system design skill. In an interview, the API is usually the first concrete thing you write after the requirements. It shows what data must exist and which operations must be fast. At work, a good API lets teams move on their own. A bad one becomes a tax that every caller pays.
Read the pages below in order. They cover the styles, the method rules, and the choices that are hard to change later.
Concepts on this topic
| Step | Concept | What it answers |
|---|---|---|
| 1 | API styles | REST, gRPC or GraphQL? |
| 2 | Safe methods and idempotency | Which calls are safe to retry? |
| 3 | Cursor pagination | How do you page a list that keeps changing? |
| 4 | API versioning | How do you change a contract without breaking callers? |
| 5 | Structured errors | What should a failed call tell the caller? |
| 6 | Webhooks and long-running operations | How do you handle work that outlives the request? |
Common mistakes
- Adding pagination later. An endpoint that returned a plain list becomes a breaking change once you paginate it. Paginate from day one.
- Unkeyed creates. A
POSTthat charges money or creates a file must accept an idempotency key, or a timeout turns into a duplicate. - Sending big files through the API. A 10 GB upload should go straight to object storage through a signed URL, not through an app server.
- Unwritten change policy. Teams that don't write down what counts as breaking end up arguing in every review.
A file-sharing API, briefly
Take a Dropbox-style service. Users upload files into folders and share them. A reasonable API looks like this:
POST /v1/folders create a folder
GET /v1/folders/{id}/children ?cursor=&limit=50, cursor paginated
PATCH /v1/files/{id} requires If-Match
POST /v1/uploads returns pre-signed part URLs
POST /v1/uploads/{id}:complete turns the parts into a file
GET /v1/files/{id}/content 302 to a short-lived signed URL
GET /v1/changes?cursor= changes feed for sync clientsThe key choices, and why:
- Uploads skip the API servers. Clients send bytes to storage. The API handles metadata only.
- Clients send
sizeandsha256first. The server can enforce quota early and detect duplicates. - Moves and renames use
If-Match. Two devices can't silently overwrite each other. - A changes feed with a cursor. Sync clients ask "what changed since I last looked?" instead of re-listing everything.
Review checklist
Use this on any API, including your own:
- Resources are nouns with stable, prefixed IDs.
- Every create or action endpoint accepts an idempotency key.
- Every list is paginated, with a total sort order and a page cap.
- Errors use one format, with stable codes and clear retryability.
- Concurrent updates are protected with ETags or version fields.
- Long work returns 202 and an operation. Webhooks are signed and deduplicable.
- Rate limits return 429 with
Retry-After.
Key takeaways
- An API is a long-lived contract. Design for additive change so you rarely need a new version.
- Make every retryable write safe with idempotency keys.
- Use cursor pagination with a unique tiebreaker, and cap the page size.
- Return structured errors with stable codes, and send large data straight to storage.
Further learning
Joshua Bloch's classic talk is about library APIs, but its principles, such as "easy to use, hard to misuse", apply to web APIs too.
A short visual explanation of RPC and gRPC, including HTTP/2 and Protobuf:
Related
HTTP/1.1, HTTP/2 and HTTP/3
The protocol of the web. Separates HTTP's meaning (methods, status codes, caching) from its wire format, and shows how each version fixes the latency problem of the one before.
Idempotency and retries
Making operations safe to retry, with idempotency keys, deduplicating consumers and jittered backoff, so at-least-once delivery never means a double charge.
Developer and API products
Treating APIs, SDKs and docs as products: developer experience and adoption.
API styles
REST, gRPC and GraphQL can answer the same request. Pick one by asking who calls the API and what they need from it.
Cursor pagination
Offset pagination skips rows and gets slower and less stable as you go deeper. Cursor pagination asks for the rows after the last one seen, so every page costs the same.