HTTP Fundamentals
HTTP is a request/response protocol with semantics layered on top of a simple message format. The semantics — safety, idempotency, caching, status meaning — are the contract your clients rely on.
The message
POST /v1/orders HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer <token>
Idempotency-Key: 7c1f...
{"items":[{"sku":"sku_1","qty":2}]}
HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/orders/ord_123
Cache-Control: no-store
{"id":"ord_123","status":"pending"}
Every part matters: method semantics, status meaning, headers for caching/auth/retries, and the body schema.
Versions
| Version | What changed | Practical impact |
|---|---|---|
| HTTP/1.1 | text protocol, keep-alive, pipelining (rarely used) | one request at a time per connection; head-of-line blocking |
| HTTP/2 | binary framing, multiplexing, header compression, server push (deprecated) | many streams per connection; still TCP head-of-line at packet loss |
| HTTP/3 | runs over QUIC/UDP, per-stream loss recovery | better on lossy/mobile networks; TLS 1.3 built in |
For APIs, the version is mostly invisible: clients care about connection reuse, multiplexing, and timeouts. Keep-alive and HTTP/2 reduce handshake cost; HTTP/3 helps mobile tails.
Methods and semantics
| Method | Safe | Idempotent | Body | Typical meaning |
|---|---|---|---|---|
GET |
yes | yes | no | read a resource |
HEAD |
yes | yes | no | metadata only |
POST |
no | no | yes | create / non-idempotent action |
PUT |
no | yes | yes | replace a resource at a known URI |
PATCH |
no | not guaranteed | yes | partial update |
DELETE |
no | yes | optional | remove a resource |
OPTIONS |
yes | yes | no | capabilities/CORS preflight |
Safe = no intended state change. Idempotent = repeating has the same effect. These are promises clients and infrastructure rely on:
- caches may serve
GETwithout contacting you; - proxies/load balancers may retry idempotent requests;
- clients may safely retry
PUT/DELETEafter a timeout.
If your POST /orders is not idempotent, retries create duplicate orders — unless you add an idempotency key.
PATCH is not idempotent in general (e.g., {"op":"increment"}); JSON Merge Patch (application/merge-patch+json) is.
Status codes that matter
| Code | Meaning | Common mistake |
|---|---|---|
200 |
OK | returning 200 for errors |
201 |
Created (+ Location) |
missing Location |
202 |
Accepted (async) | using 200 for queued work |
204 |
No content | sending a body anyway |
301/308 |
Permanent redirect | changing method semantics accidentally (308 preserves) |
304 |
Not modified | ignoring conditional requests |
400 |
Bad request | using it for validation vs malformed |
401 |
Unauthenticated | missing WWW-Authenticate |
403 |
Authenticated but forbidden | leaking existence of resources |
404 |
Not found | using 403 to hide, inconsistently |
409 |
Conflict | using 400 for concurrency conflicts |
412 |
Precondition failed | ignoring If-Match |
422 |
Semantically invalid | mixing with 400 arbitrarily |
429 |
Rate limited | no Retry-After |
500 |
Server bug | leaking stack traces |
502/503/504 |
Upstream/unavailable/timeout | retrying non-idempotent calls |
Clients branch on status families first (2xx, 4xx, 5xx), then on the error code in the body. Be consistent.
Headers you will actually use
| Header | Purpose |
|---|---|
Content-Type |
body format (application/json; charset=utf-8) |
Accept / Accept-Encoding |
content negotiation (gzip, br) |
Authorization |
bearer token / basic / signature |
Cache-Control |
caching policy (no-store, max-age, s-maxage) |
ETag / If-None-Match |
conditional GET, optimistic concurrency |
If-Match |
optimistic locking (412 on mismatch) |
Location |
created resource / redirect |
Retry-After |
backoff hint on 429/503 |
Idempotency-Key |
deduplicate retried writes (convention) |
X-Request-Id |
correlation across services |
Vary |
which request headers change the cache key |
Caching
Two mechanisms:
Freshness (Cache-Control):
Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=30
Validation (ETag):
ETag: "v42"
If-None-Match: "v42" -> 304 Not Modified (no body)
Use validation for user-specific or frequently changing data; use freshness for static/public data. Never cache authenticated responses publicly. Handle Vary carefully (Accept-Encoding, Accept-Language), or caches will serve the wrong variant.
Content negotiation and compression
- Support JSON first; add others only with a real consumer.
- Compress large bodies (
gzip/br) and setVary: Accept-Encoding. - Respect
Accept; return406if you cannot satisfy it, or fall back explicitly. - Avoid compression for tiny payloads and secrets where BREACH-style attacks matter.
Connections, timeouts, and retries
HTTP is built on TCP (or QUIC). Consequences:
- Handshakes are expensive: reuse connections (keep-alive), pool them, and set idle timeouts above the upstream's.
- Timeouts are mandatory: connect, read, and total. A client without a timeout can hang forever.
- Retries need idempotency: retry
GET/PUT/DELETEand idempotency-keyedPOST; do not blindly retryPOST. - Backoff with jitter avoids synchronized retry storms.
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json", "Idempotency-Key": key },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(2_000),
});
Cookies vs tokens
- Cookies are automatically attached by browsers; require
Secure,HttpOnly,SameSite, and CSRF protection for state-changing requests. - Bearer tokens are explicit; must be protected from XSS, scoped, short-lived, and rotatable.
- Never put secrets in URLs (they leak in logs/referrers).
Common mistakes
- Returning
200with{"error": ...}— breaks every generic client, cache, and monitor. - Using
GETfor state changes — caches and prefetchers will execute them. - No
Idempotency-Keyon a retryablePOST. - Unbounded lists and no pagination.
- Caching personalised responses publicly.
- Inconsistent error shapes across endpoints.
- Missing
Varyand serving gzip to clients that cannot decode it. - No timeout on outbound calls.
Checklist
- Methods match safety/idempotency semantics.
- Status codes are correct and consistent; errors have stable codes.
-
Content-Type,Accept, and compression are handled. - Mutations define retry/idempotency behaviour.
- Caching policy is explicit (
no-storefor authenticated data). - Timeouts and backoff are configured on clients.
- Cookies/tokens set
Secure/HttpOnly/SameSitewhere applicable. - Request ids propagate for debugging.
Next: REST Fundamentals — resources, representations, and the parts of REST worth keeping.