Handbooks  /  API Design
Engineering~13 min readIntermediate
Deep Dive

API design: an API is a promise you keep for years.

Code you can refactor; an API you can only regret. The moment a second team — or a paying customer, or someone's autonomous agent — builds against your interface, every field name becomes a commitment and every sloppy semantic becomes someone's production incident. This handbook is about making promises deliberately: which paradigm, which semantics, and how to evolve without breaking the people who trusted you.

01

Three paradigms, one honest decision

ParadigmIts actual superpowerReach for it when
RESTuniversality — every language, proxy, cache and curl understands itpublic APIs, partner integrations, anything a browser touches
gRPCtyped contracts + HTTP/2 + streaming + codegeninternal microservices, polyglot teams, latency-sensitive paths
GraphQLclient-shaped responses from one endpointmany frontends with different data needs over one graph

The pattern that actually ships: REST at the edge, gRPC inside — public simplicity where strangers integrate, typed efficiency where your own services chat thousands of times per second. GraphQL earns its complexity only when the many-frontends problem is real; adopted for fashion, it hands you resolver N+1s, cache invalidation pain and query-cost policing for nothing in return.

02

REST done properly: resources, not verbs

REST's discipline is modeling: nouns in URLs, verbs from HTTP. GET /orders/17, POST /orders, DELETE /orders/17 — not /getOrder, /createOrder, /cancelOrder2. Sub-resources for containment (/orders/17/items), query params for filtering and sorting, and status codes used to mean what they mean: 201 with a Location on create, 404 vs 403 chosen deliberately, 422 for validation, 429 with Retry-After.

→ The test of a good resource model

A new consumer can guess your endpoints. If they know /orders exists, they should correctly predict how to list, fetch, create, and filter. Every endpoint that needs explaining is a design debt payment someone makes forever.

Actions that genuinely aren't CRUD (cancel, approve, retry) get modeled honestly as sub-resources or action endpoints (POST /orders/17/cancellation) — the sin isn't the exception, it's pretending everything fits and shipping PUT /orders/17?action=cancel&mode=3.

03

gRPC: the contract-first workhorse

gRPC inverts the workflow: you write the protobuf contract first — messages and services in a .proto file — and generate clients and servers in every language from it. The wire format is compact binary, the transport is HTTP/2 (multiplexed streams, one connection), and you get four call shapes for free: unary, server-streaming, client-streaming, bidirectional. For service-to-service traffic it's simply better physics than JSON-over-HTTP/1.1 — smaller, faster, typed, and impossible to misspell a field against.

Its evolution rules are beautifully mechanical: fields are identified by number, not name — so renames are free, additions are safe, and the only sins are reusing a number or changing its type. Mark removed fields reserved and old clients keep working forever. (The costs: browsers need a proxy like gRPC-Web or a REST gateway, and debugging binary needs tooling, not curl.)

04

Semantics that survive the real network

Networks time out, clients retry, and your API's behavior under retry IS its correctness. The load-bearing semantics:

The reliability contract
Idempotency keysclient sends a unique key per logical operation; server stores + replays the result on retry. Mandatory wherever money moves or resources spawn.
Cursor paginationopaque continue-from tokens, not offsets — stable under writes, constant-time at any depth.
Rate-limit headers429 + Retry-After + X-RateLimit-Remaining — teach clients to back off instead of guessing.
Structured errorsmachine-readable code, human message, correlation id (Problem+JSON shape) — an error a program can branch on and a human can debug.
Timeouts + deadlinesaccept a client deadline and propagate it downstream (gRPC does this natively) — no zombie work for callers who already gave up.
05

Evolution: breaking up without breaking clients

The versioning hierarchy, best to worst: don't need it (additive-only changes — new optional fields, new endpoints — break no one; make additive the team default), explicit versions (/v2 in the path: ugly, honest, cache-friendly), header-based versions (cleaner URLs, invisible in logs and browsers — a mixed blessing). What kills APIs isn't versioning strategy; it's silent breaking changes: a renamed field, a tightened validation, a "harmless" change of default ordering. Contract tests against recorded consumer expectations catch these; changelogs and deprecation windows (with usage metrics on the old path) retire things like an adult.

06

The newest consumer: agents

A quiet shift: the fastest-growing consumer of APIs isn't developers reading your docs — it's LLM agents reading your schemas. A tool schema is an API contract whose documentation IS the interface: the model chooses and fills your endpoint based purely on names, descriptions and types. Everything in this handbook compounds there — guessable resource models, precise types, structured errors an agent can recover from, idempotency because agents retry enthusiastically. MCP is this idea standardized. Design your API so a stranger can guess it, and you've designed it so a model can use it.

07

The checklist

DecisionDefault
Edge protocolREST + JSON, resource-modeled, guessable
Internal protocolgRPC, contract-first, deadlines propagated
Writesidempotency keys on anything non-idempotent
Listscursor pagination, capped page sizes
Errorsmachine code + human message + correlation id
Changesadditive by default; explicit /v2 when not; deprecation with metrics
DocsOpenAPI/proto as source of truth — humans AND agents read it
Frequently asked

Quick answers

REST vs gRPC vs GraphQL?

REST at the edge (universal), gRPC inside (typed, fast, streaming), GraphQL only when many frontends genuinely need differently-shaped data.

Why idempotency keys?

Networks fail, clients retry. A stored-and-replayed key per logical operation makes retries safe — mandatory where money moves.

Cursor vs offset pagination?

Offsets shift under writes and slow with depth; cursors continue from a stable position in constant time.

How to change an API safely?

Additive by default, explicit /v2 when breaking, deprecation windows with usage metrics, contract tests against consumers.

API Design · Engineering · Vibe Engines · 2026
Finished this one? 0 / 49 Handbooks done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.