8 lessons

HTTP Semantics

Methods as promises: safety, idempotency, and what retries, proxies and caches are allowed to assume. Status codes that mean something, conditional requests, and caching as part of the contract.

RequirementConsumersResource ModelStyleContractValidationAuthorizationErrorsIdempotencyPaginationVersioningObservabilityEvolutionTrade-offs

Every lesson below names the consumers, the design question and the guarantee before recommending anything. Recommendations come with what they cost, when not to use them, and how they evolve.

HTTP Methods Are Promises
▶ lab

Safe means "calling this changes nothing"; idempotent means "calling this twice equals calling it once". Retrying clients, proxies, caches and crawlers all act on those promises without asking — which is why breaking them breaks things you have never heard of.

Q · What is each HTTP method allowed to promise about side effects and repetition — and which infrastructure is already acting on that promise?
GET: The Promise of Safety

GET promises that reading changes nothing — a promise browsers, caches, crawlers and prefetchers spend billions of requests a day relying on. GET /deleteUser?id=42 is not a style violation; it is an open invitation to every robot on the internet.

Q · Can every GET in this API be issued by anything, any number of times, at any moment, without changing state anyone is accountable for?
POST: More Than Create

POST is HTTP's "here, process this" — creation, commands, complex reads, batch submissions. Its defining property is what it refuses to promise: idempotency. Every POST that matters needs an answer to "what if this arrives twice?", because it will.

Q · This operation is not safe and not idempotent by method — so what is the application-level answer when the same POST arrives twice?
PUT vs PATCH

PUT replaces the whole representation and is idempotent by construction; PATCH applies a partial change and is only as safe as your merge rules. The hard part is not choosing between them — it is saying what null means, and what absent means.

Q · When a client updates this resource, is it stating the complete desired state (PUT) or requesting a delta (PATCH) — and does the contract define what null and absent each mean?
DELETE: What Does Gone Mean?

Hard delete, soft delete, async purge — three different promises hiding behind one method. DELETE is idempotent (the retry that gets 404 still succeeded), but what deletion *means* — recoverable? invisible? eventually erased? — is a domain contract HTTP cannot write for you.

Q · When a client DELETEs this resource, what is actually promised — immediate erasure, hidden-but-recoverable, or a purge process — and what does a retry see?
Status Codes Clients Can Branch On
▶ lab

The first digit answers "who acts next?" — that is the real contract. You need the dozen codes clients actually branch on, used honestly, far more than you need the other forty memorized.

Q · For every response this API sends, does the status code correctly tell the client — and every cache, proxy and monitor in between — who should act next, and how?
Conditional Requests: ETags, 304 and 412

One mechanism, two superpowers: If-None-Match turns repeat reads into 200-byte 304s, and If-Match turns racing writes into honest 412s. The validator — the ETag — is a contract about when a representation counts as changed.

Q · Can clients ask "has this changed?" instead of re-downloading, and say "apply this only if unchanged" instead of overwriting — and what exactly does the ETag promise?
Caching as a Contract Clause

Cache-Control is not a performance knob — it is a promise about staleness: who may store this response, for how long, and what "fresh enough" means. The most expensive header in HTTP is the one that let a shared cache store a private response.

Q · For each response: who is allowed to cache it, for how long may they serve it without asking, and what staleness has the consumer actually agreed to?