Section A · Orient

Start Here

One idea holds this whole guide together: an API is a contract you cannot take back. The shapes you publish become code other people ship, and from that moment they are yours to keep stable. Everything that follows — the style, the resources, the errors, the versioning — is a way to make that contract easy to use now and safe to change later. This page gives you the idea, the example we carry throughout, and three ways to read.

What an API really is

An API — an application programming interface — is the set of shapes one program offers so other programs can use it without knowing how it works inside. A web API is that set of shapes offered over HTTP: the URLs you can call, the methods you can call them with, the JSON that goes up, the JSON that comes back, and the rules for what happens when something is wrong. A caller on the far side of the network reads those shapes and writes code against them. They never see your database, your queue, or the services behind your gateway. They see links they can create and clicks they can read.

The thing that makes API design different from writing a function is that you do not control the caller, and you cannot change both sides at once. When you rename a parameter in your own code, the compiler finds every call site and you fix them in one commit. When you rename a field in a public API, the call sites belong to strangers, they are already running in production, and you find out you broke them from the support queue. So the central fact of this guide is simple and unforgiving: once someone depends on a shape, that shape is a promise. Good API design is the craft of choosing promises you can live with, and leaving room to add new ones without breaking the old.

That is why the work front-loads. The cost of a bad internal function is an afternoon's refactor. The cost of a bad public field is every integration that read it, a deprecation cycle measured in months, and a migration you run on other people's behalf. You are not optimising for the first caller; you are optimising for the thousandth, and for the version of the API you will wish you had shipped two years from now.

Control plane vs data plane

Before any shapes, one distinction decides how you think about the whole system: the control plane and the data plane are different APIs with different rules, and conflating them is the most common early mistake.

Snip's data plane is the redirect: a browser hits snip.ly/a3Xk9 and gets a 302 to the long URL. That path runs 100 million times a day, has to answer in a few milliseconds, and is not programmable — nobody writes code against it; browsers just follow it. The Designing for Scale guide is entirely about that path: caching, replicas, shards, the lot.

Snip's control plane is the API this guide designs: POST /v1/links to create a short link, GET /v1/links/{code}/clicks to read its analytics, PATCH to change where it points. That path runs far less often — roughly a million writes a day, a few dozen a second — but every call is code a developer wrote, every field is a contract, and getting the shapes right matters more than shaving a millisecond. The two planes even scale in opposite directions: the data plane is read-heavy and latency-critical; the control plane is write-heavier, lower-volume, and correctness-critical.

When people say "design the API," they almost always mean the control plane — the programmable surface other teams build on. The high-traffic data path is a systems problem (that is the other guide). Keep them separate in your head and you will stop trying to make one set of rules serve both.

The running example: Snip's API

Rather than invent a new system each chapter, we carry one the whole way: the public API for Snip, a URL shortener with click analytics. Snip already exists as a running service — the scalability guide built it — and now other teams want in. The marketing team wants to create tracked links from their campaign tool. A partner wants to build a dashboard on Snip's click data. A customer wants a webhook when one of their links goes viral. All of that is the job of the API.

Here is the surface we will design, referenced the same way in every chapter. Treat this as the canon; chapters add detail but never contradict it.

  • Base URL and version: https://api.snip.ly/v1. The major version lives in the path.
  • Resources: a link (fields: code — a base62 7-character id like a3Xk9bQ; url; created_at; tags; total_clicks, which is eventually consistent) and its clicks (GET /v1/links/{code}/clicks, an analytics sub-resource).
  • Core calls: POST /v1/links (create, returns 201 + Location), GET /v1/links/{code} (read one), GET /v1/links (list, cursor-paginated), PATCH /v1/links/{code} (change the destination), DELETE /v1/links/{code} (returns 204).
  • Auth: API keys (Authorization: Bearer sk_live_…) for a team's own server-to-server calls; OAuth 2.0 with PKCE for third-party apps acting for a user. Tokens carry scopes — links:read, links:write, analytics:read.
  • Errors: RFC 9457 problem details — a JSON body with type, title, status, detail, and a field-level errors array.
  • Limits: rate limits reported in RateLimit-Limit/RateLimit-Remaining/RateLimit-Reset headers; over the limit returns 429 with Retry-After.
  • Events: a link.clicked webhook (batched and sampled, not one per redirect), signed with an HMAC in Snip-Signature, delivered at-least-once so the consumer must de-duplicate on event id.

A first call, so the shape is concrete:

curl -X POST https://api.snip.ly/v1/links \
  -H "Authorization: Bearer sk_live_8f2a…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5c1f3e9a-1b2c-4d5e-8f90-a1b2c3d4e5f6" \
  -d '{"url": "https://example.com/a/very/long/path?utm=spring", "tags": ["spring-campaign"]}'
HTTP/1.1 201 Created
Location: https://api.snip.ly/v1/links/a3Xk9bQ
Content-Type: application/json

{
  "code": "a3Xk9bQ",
  "url": "https://example.com/a/very/long/path?utm=spring",
  "short_url": "https://snip.ly/a3Xk9bQ",
  "tags": ["spring-campaign"],
  "total_clicks": 0,
  "created_at": "2026-03-14T09:21:00Z"
}

Every chapter picks up some part of this: chapter 01 justifies why it is REST and not gRPC; chapter 02 explains why POST /v1/links and not POST /v1/createLink; chapter 08 explains that Idempotency-Key header; chapter 09 designs the webhook. Chapter 10 puts the whole surface on one page.

What good looks like

It helps to name the target before the details. A well-designed API has a few properties you can feel the moment you use one, and their absence the moment you fight one:

  • Predictable. Once you have used one endpoint, you can guess the next. Collections are plural nouns, GET never changes anything, the same error shape comes back everywhere. The caller builds a mental model once and it keeps paying off.
  • Hard to misuse. The obvious call is the correct call. A retried create does not make two links. A missing required field comes back as a clear 400 that names the field, not a 500 three services deep.
  • Evolvable. You can add a field, a resource, or an optional parameter without a new version and without breaking a single existing caller. The design left room on purpose.
  • Honest about failure. Errors are machine-readable, the status code matches what happened, and the caller can tell "you sent something wrong" (fix your request) from "we broke" (retry later) without parsing prose.

Notice none of these is about performance. A fast API with an unpredictable surface and surprise breaking changes is a worse API than a slightly slower one that a team can integrate in an afternoon and trust for years. Speed is the other guide's subject; this guide is about the surface.

How this guide is built

Eleven chapters in five sections. Section B shapes the surface: choosing a style (REST, gRPC, GraphQL), resource modeling, and pagination, filtering and fields. Section C is how the contract survives contact with reality: errors and validation, then versioning and evolution. Section D is how you protect it: auth and scopes, rate limits, caching, concurrency and idempotency, and async delivery. Section E is one worked design that spends every idea on Snip.

Each chapter leads with the mechanism — what the request and response actually carry — then the consequence for your choices, then where it lands on Snip's API. Recommendations come with the case where they flip. If you only have time for the core, read 00 through 04 and you will have a usable API; read 05 before you ship it, because the first version is the one you are stuck with.

Takeaway

  • An API is a contract you cannot take back. Once a caller depends on a shape, that shape is a promise, and you cannot change both sides of the network at once.
  • That is why API work front-loads: the cost of a bad public field is every integration that read it plus a months-long deprecation, not an afternoon's refactor.
  • Separate the control plane (the programmable API this guide designs) from the data plane (the high-traffic redirect, which is a systems problem). They have opposite shapes and different rules.
  • Aim for four properties: predictable, hard to misuse, evolvable, honest about failure. None of them is speed.
  • We carry one example — Snip's public API — because creating links, paging clicks, authing partners, and delivering webhooks exercises the whole syllabus.

Next, the first and most consequential shape decision — the style the whole API is built in: Choosing a Style →