
HTTP isn’t just “how browsers talk to servers.” It’s a mature distributed-systems contract with semantics for caching, retries, concurrency, intermediaries, and evolution. If you design APIs without those semantics, production will teach you them anyway.
Axel Domingues
Last month we talked about the web’s “compression algorithm”: how the industry keeps moving work between server → client → server again (SSR) → edge to satisfy new constraints.
This month is the substrate that makes every one of those eras possible:
HTTP.
Not as “requests and responses.”
As a distributed systems protocol that:
If you ignore those semantics, you don’t get to avoid them.
You just get to learn them during an incident.
Give you a practical mental model and a set of checklists so you can design HTTP APIs that survive:
HTTP is a message protocol with semantics designed for an ecosystem of intermediaries.
That ecosystem matters.
Because your “client → server” diagram is almost never true in production.

When you send a request, you don’t hit “the server.”
You traverse a graph.
Each hop can:
So the right question isn’t:
“What does my server do when it receives a request?”
It’s:
“What semantics does my system promise when the request graph behaves like the real internet?”
If you’re thinking “this is distributed systems vocabulary,” yes.
That’s the point.
HTTP isn’t just bytes over TCP.
It gives you semantics that you can build systems on top of—if you stop fighting them.
Method semantics
Safety + idempotency determine what can be retried, cached, and replayed.
Caching semantics
Cache-Control, validators (ETag), and Vary let you scale reads without lying.
Status semantics
Status codes tell clients and intermediaries what happened and what to do next.
Content semantics
Representation, negotiation, and headers let you evolve contracts without breaking everyone.
In practice, teams mostly learn these semantics only after they shipped an API that violates them.
Let’s not do that.
Here’s the rule architects keep in their pocket:
The retry rule
If an operation can be retried, it must be safe or idempotent (or have an idempotency key).
Why? Because timeouts aren’t “didn’t happen.”
Timeout means:
So if you design an endpoint like this:
POST /charge-card…and it’s not idempotent, you just wrote a payment duplication bug.
Use for resource retrieval and queries.
Design expectations
Common mistakes
Use when the server assigns identity or the action is non-idempotent by default.
Design expectations
Good patterns
POST /orders to create a new orderPOST /orders/{id}/submit as a command (explicitly a state transition)Use when the client knows the resource identity and sends the full desired state.
Design expectations
Common mistakes
Use for partial updates when replacement is wasteful.
Design expectations
Common mistakes
Design expectations
Common mistakes
We’re not choosing verbs for style.
We’re choosing semantics that define retry safety, caching, and operability.
Caching is not an optimization.
Caching is a feature that:
HTTP gives you a caching language. Use it.
Browser cache
Fastest path. But can be polluted by auth and shared devices if mishandled.
CDN / edge cache
Your biggest scalability lever for public content and stable resources.
Reverse proxy cache
Great for shielding origins, but needs strong cache key discipline.
Application cache
Redis/memory caches. Powerful, but now you own invalidation and coherence.
If you’re unsure, start with:
VaryCache-Control: private or no-store until proven safeIt’s serving the wrong user’s data. That’s how performance incidents become security incidents.
Conditional requests let clients and CDNs ask:
“Has this changed?”
Without refetching the whole payload.
That’s how you reduce bandwidth and improve perceived speed while keeping correctness.
The practical win
ETags + 304 responses are a bandwidth and latency reducer that also improves stability under load.
Vary).Status codes aren’t for humans.
They’re signals to:
A few status patterns separate “clean systems” from “painful systems.”
2xx — success with meaning
Return 201 for creation, 202 for accepted async work, 204 for empty success.
4xx — client action required
Use 400/401/403/404/409/422 intentionally to communicate what to fix.
5xx — server fault (and retriable signals)
Differentiate “try again later” vs “we’re broken” with your error policy.
429 — throttling
Rate limits need contracts (headers, retries, budgets), not surprise failures.
If clients can’t distinguish:
HTTP failures are not binary.
They’re ambiguous.
This is why method semantics + idempotency are architecture concerns.
Retries should be designed like spending money:
Retry design rule
Retries must have a limit, backoff, and jitter—or they become a denial-of-service you wrote yourself.
A single hop timeout is not enough.
Define a deadline for the whole request path (client → edge → origin → upstreams). Every layer must honor it.
If thousands of clients retry on the same schedule, you create synchronized load spikes.
Backoff spreads load. Jitter prevents thundering herds.
Retry rate is an SLO smell. If it climbs, you’re masking a deeper reliability issue.
When a dependency slows down:
Now we combine the pieces into a practical design posture.
The architecture posture
Design HTTP APIs assuming intermediaries exist, clients retry, caches cache, and failures are ambiguous.
Semantics
Caching
Vary is explicit and safe.Errors
Resilience
Security
These are the ones I keep seeing across teams and companies.
Typical root cause
Fix
Typical root cause
Vary behaviorFix
private / no-store where neededVaryTypical root cause
Fix
Typical root cause
Fix
A lot of “microservices architecture” debates happen because teams skip fundamentals.
But HTTP already solved many system-level concerns at the protocol level:
When you respect HTTP semantics, your system becomes easier to:
When you ignore them, you’ll re-invent them—badly—inside application code.
February takeaway
HTTP is not “transport.” It’s distributed systems semantics that you either use intentionally or learn painfully.
MDN — HTTP Overview
A pragmatic, developer-friendly guide to methods, headers, caching, and status codes.
HTTP Semantics (RFC 7231)
The canonical semantics: methods, status codes, negotiation—useful when you need “the source of truth”.
Not really.
This is about HTTP semantics—which REST uses, but which also apply to “RPC-ish” APIs.
You can build a pragmatic API style and still benefit from:
No.
But you do need consistency and clarity—especially for:
Status codes are less about purity and more about client behavior under failure.
Treat ambiguity as normal.
Assume:
Then design your operations so repeating them doesn’t break the world.
HTTP is the contract.
Next month we talk about the runtime that consumes it:
“Browser Reality: The Event Loop, Rendering, and Why UX Bugs Look Like Backend Bugs”
Because once you understand HTTP semantics, your next most common production failures are no longer “API bugs.”
They’re timing and scheduling bugs—in the browser.
Browser Reality: The Event Loop, Rendering, and Why UX Bugs Look Like Backend Bugs
The browser is a constrained runtime with a scheduling problem: one main thread, many responsibilities, and users who notice missed frames. This post gives you the mental model to debug “random” UX failures as deterministic timing and contention issues.
The Web's "Compression Algorithm": Static → Web 2.0 → SPA → SSR/Edge
The web didn’t evolve because developers got bored. It evolved because latency, state, and economics kept forcing us to move responsibility between server, client, and edge. This post gives you the mental model and the checklist to choose the right rendering architecture in 2021+