HTTP Semantics Methods and Status Codes

HTTP semantics — the meaning of a request and its response, independent of how the bytes are framed on the wire — is specified by RFC 9110 (“HTTP Semantics”, June 2022, Internet Standard STD 97), which consolidated and obsoleted the older RFC 7230-series and RFC 723x definitions. Its central architectural move is stated in §1.2: “This revision of HTTP separates the definition of semantics (this document) and caching from the current HTTP/1.1 messaging syntax to allow each major protocol version to progress independently” (RFC 9110 §1.2). Every version — 1.1, 2, 3 — reuses the same methods (GET, POST, PUT, …), the same three-digit status codes, and the same header-field semantics; only the encoding differs. This note is the protocol / RFC view of that shared vocabulary: the request methods and their safe / idempotent / cacheable properties, the five status-code classes, content negotiation and representations, conditional requests, and range requests.

This note owns the version-independent semantics. The wire encodings live in the version notes (HTTP 2 Multiplexing and HPACK, HTTP 3 over QUIC); the caching rules that lean on these semantics live in HTTP Caching (RFC 9111); state carried across requests lives in HTTP Cookies and State Management.


Mental Model — Semantics Above, Syntax Below

The single most important idea in RFC 9110 is the layering of meaning over encoding. A “GET the resource, respond 200 with this representation” is an abstract transaction. HTTP/1.1 writes it as ASCII text with a blank-line-delimited header block; HTTP/2 writes it as HPACK-compressed binary HEADERS frames; HTTP/3 writes it as QPACK frames over QUIC. The transaction — the method, the target URI, the status, the header semantics — is identical in all three. RFC 9110 is the contract that makes that true, which is why you can put an HTTP/1.1 origin behind an HTTP/2 reverse proxy and lose nothing: the proxy is translating syntax, not semantics.

flowchart TB
  subgraph SEM["RFC 9110 — version-independent SEMANTICS"]
    M["Methods<br/>GET / POST / PUT / …"]
    S["Status codes<br/>1xx–5xx"]
    F["Field semantics<br/>Accept, ETag, Vary, Range, …"]
    R["Representations<br/>+ content negotiation"]
  end
  subgraph SYN["Per-version SYNTAX (messaging)"]
    H1["RFC 9112<br/>HTTP/1.1 text"]
    H2["RFC 9113<br/>HTTP/2 binary frames + HPACK"]
    H3["RFC 9114<br/>HTTP/3 over QUIC + QPACK"]
  end
  SEM --> H1
  SEM --> H2
  SEM --> H3

How RFC 9110 sits above the wire formats. What it shows: one body of semantics feeds three interchangeable encodings. The insight to take: when you reason about idempotency, caching, redirects, or status-code meaning, the HTTP version is irrelevant — those are RFC 9110 facts. When you reason about head-of-line blocking, header compression, or multiplexing, the version is everything — those are RFC 9112/9113/9114 facts. Keeping the two straight is the difference between understanding HTTP and memorizing it.


Methods — Safe, Idempotent, Cacheable

A request method is a token that tells the origin server what the client wants done with the target resource (the resource identified by the request URI). RFC 9110 §9 defines eight; a ninth, PATCH, is defined separately in RFC 5789. Three orthogonal properties classify them, and interviewers love the distinctions because they are routinely conflated.

Safe (§9.2.1): a method is safe if “their defined semantics are essentially read-only; that is, the client does not request, and does not expect, any state change on the origin server” (RFC 9110 §9.2.1). Safe is a contract about intent, not a guarantee — a GET may increment a hit counter server-side, but the client is not asking for that. Safety is what lets crawlers, prefetchers, and link-preview bots follow GET/HEAD links freely: a well-behaved bot will never POST.

Idempotent (§9.2.2): “A request method is considered idempotent if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request.” Idempotency is the property that makes automatic retry safe. If a PUT times out, the client can resend it; whether the first attempt landed or not, the end state is the same. POST is not idempotent — resending a “create order” POST may create two orders — which is why retry logic and payment systems reach for idempotency keys (see Retries Backoff and Idempotency at the Protocol Layer). Every safe method is idempotent; the converse is false (PUT and DELETE are idempotent but not safe).

Cacheable (§9.2.3): a method’s responses are allowed to be stored and reused by a cache. GET and HEAD are cacheable by default; POST responses are cacheable only when explicitly marked with freshness information, and in practice almost never are; PUT, DELETE, CONNECT, OPTIONS, and TRACE are not cacheable. The full rules are in HTTP Caching.

MethodSafeIdempotentCacheablePurpose
GETRetrieve a representation of the target resource
HEADLike GET but headers only — no response body
POSTonly if explicit freshnessProcess the enclosed representation (create, submit, RPC)
PUTReplace the target resource with the enclosed representation
DELETERemove the target resource
PATCH (RFC 5789)✘*Apply a partial modification
OPTIONSQuery the communication options for a resource
CONNECTEstablish a tunnel (e.g. HTTPS through a proxy)
TRACELoop-back the request for diagnostic path tracing

The safe/idempotent/cacheable columns are RFC 9110 §9.3 and the method registry. PATCH is not idempotent per RFC 5789 §2 — a patch document may describe a relative change (“append X”) — though a patch can be made idempotent by expressing it as an absolute assertion; this is a design choice, not a property of the method.

Two subtleties bite people. First, PUT vs POST is about idempotency and target semantics, not about “update vs create.” PUT means “make the resource at this exact URI be this”; if it exists you replace it, if not you may create it — either way, doing it twice yields one resource. POST means “process this at this resource” and the server decides what URI (if any) results. Second, TRACE is a security liability (it reflects request headers, enabling Cross-Site Tracing) and is disabled on virtually every production server; CONNECT is the method your browser sends to a forward proxy to open a raw TCP tunnel for TLS, which is why proxies see CONNECT example.com:443 but never the encrypted bytes inside.


Status Codes — Five Classes

Every response carries a three-digit status code whose first digit names one of five classes (§15). RFC 9110 stresses that clients “MUST understand the class of any status code, as indicated by the first digit,” and treat an unrecognized code as the generic x00 of its class — so a client that has never heard of 418 treats it like 400. The IANA HTTP Status Code Registry is the authoritative list; RFC 9110 defines the core, and a handful come from satellite RFCs.

1xx Informational — interim responses; the final response still follows.

  • 100 Continue — the server has read the request headers and the client may send the (large) body. Triggered by the client sending Expect: 100-continue, letting it avoid uploading a gigabyte before learning the server would 401 it.
  • 101 Switching Protocols — the server agrees to change protocol (the WebSocket upgrade handshake; see WebSocket Protocol).
  • 103 Early Hints (RFC 8297, Dec 2017) — lets the server send Link: rel=preload hints before the final response is computed, so the browser can start fetching CSS/JS during server think-time. This is the modern replacement for HTTP/2 server push (see HTTP 2 Multiplexing and HPACK). RFC 8297 stresses these hints “do not replace the header fields on the final response.”

2xx Successful.

  • 200 OK — the everyday success; carries a representation.
  • 201 Created — a new resource was created; the Location header points at it.
  • 202 Accepted — the request was accepted for asynchronous processing; completion is not guaranteed (the async-job pattern).
  • 204 No Content — success with a deliberately empty body (a form submission or PUT where the response body would be redundant).
  • 206 Partial Content — the successful answer to a range request (see below).

3xx Redirection — the classic exam trap. The four “moved” codes differ on two axes: permanent vs temporary, and whether the client may rewrite the method to GET.

CodePermanenceMethod on redirect
301 Moved Permanentlypermanenthistorically often rewritten to GET
302 Foundtemporaryhistorically often rewritten to GET
303 See Otheralways GET the Location (Post/Redirect/Get)
307 Temporary Redirecttemporarypreserved (a POST stays a POST)
308 Permanent Redirectpermanentpreserved

RFC 9110 is explicit that with 301 and 302, older user agents erroneously changed the method to GET, and 307/308 exist precisely to say “redirect but keep the method and body.” 304 Not Modified is technically 3xx but semantically part of caching — it tells a conditional requester “your cached copy is still good,” and is covered under conditional requests below and in HTTP Caching.

4xx Client Error — the request was wrong.

  • 400 Bad Request — malformed request the server won’t process (the catch-all).
  • 401 Unauthorized — authentication is required or failed; the server must send WWW-Authenticate. (Misnamed: it means unauthenticated.)
  • 403 Forbidden — the server understood and refuses; authenticating will not help.
  • 404 Not Found — no resource at this URI (and the server won’t say whether it ever existed).
  • 405 Method Not Allowed — the URI exists but not for this method; must send Allow.
  • 406 Not Acceptable — no representation matches the client’s Accept* constraints.
  • 409 Conflict — the request conflicts with current state (e.g. an edit against a stale version).
  • 410 Gone — like 404 but intentionally permanent: the resource was deliberately removed and will not return.
  • 412 Precondition Failed — a conditional request’s precondition (e.g. If-Match) evaluated false.
  • 415 Unsupported Media Type — the server won’t accept the request body’s Content-Type.
  • 416 Range Not Satisfiable — the requested byte range lies outside the resource.
  • 422 Unprocessable Content — syntactically valid but semantically wrong (well-formed JSON that violates business rules). Note: 422 now lives in RFC 9110 §15.5.21, moved in from WebDAV’s RFC 4918.
  • 429 Too Many Requests (RFC 6585) — rate limiting; SHOULD carry Retry-After.
  • 418 (Unused) — the “I’m a teapot” code from the April-Fools RFC 2324; RFC 9110 §15.5.19 reserves it as (Unused) so it will never be assigned, precisely because so much software hard-codes it as a joke.
  • 451 Unavailable For Legal Reasons (RFC 7725) — blocked for legal/censorship reasons (the number nods to Fahrenheit 451).

5xx Server Error — the request was fine; the server failed.

  • 500 Internal Server Error — the generic “something broke” (an unhandled exception).
  • 501 Not Implemented — the server doesn’t support the functionality (e.g. an unknown method).
  • 502 Bad Gateway — a proxy/gateway got an invalid response from upstream.
  • 503 Service Unavailable — temporarily overloaded or down for maintenance; SHOULD carry Retry-After.
  • 504 Gateway Timeout — a proxy/gateway timed out waiting for upstream.

The 502/503/504 distinction matters operationally: 502 means the upstream answered garbage, 504 means it didn’t answer in time, 503 means this server is deliberately shedding load. A load balancer returning 502 is a different incident from one returning 504.


Representations and Content Negotiation

RFC 9110 is careful to say the server does not transfer resources — it transfers representations. A “representation is information that is intended to reflect a past, current, or desired state of a given resource, in a format that can be readily communicated via the protocol” (§3.2). One resource (/report) can have many representations: HTML or JSON, English or French, gzip’d or plain. Content negotiation is how client and server agree on which one.

Proactive negotiation (§12.1) is the common case: the client states preferences in request headers and the server picks. The knobs are Accept (media types like application/json), Accept-Language (en-US), Accept-Encoding (gzip, br), and Accept-Charset. Each accepts a quality value — a q= weight from 0 to 1 — so Accept: text/html;q=0.9, application/json;q=1.0 says “JSON preferred, HTML acceptable.” The server answers with the corresponding Content-Type, Content-Language, and Content-Encoding describing what it actually sent. Reactive negotiation (§12.2), where the server returns a list of alternatives for the client to choose, exists but is rarely used.

The load-bearing header for anyone running a cache is Vary (§12.5.5). When a response was selected by negotiation, Vary names the request headers that drove the choice — Vary: Accept-Encoding, Accept-Language. This tells a shared cache: do not reuse this stored response for a later request unless those named headers match. Omit it and a cache will happily hand a gzip-encoded body to a client that never sent Accept-Encoding: gzip, or serve the French page to an English speaker. Vary is the seam between semantics (here) and caching (there); HTTP Caching covers how the cache uses it to build a secondary cache key.


Conditional Requests — Validators, ETag, Last-Modified

A conditional request carries a precondition that the server evaluates before acting; if it fails, the server short-circuits (§13). The universal use is cache revalidation: “send me the resource only if it changed since I last saw it.” The machinery rests on validators — opaque tokens that identify a representation’s state (§8.8).

An ETag (entity tag, §8.8.3) is a server-generated opaque string identifying a specific representation — often a content hash or version counter, e.g. ETag: "a3f9c2". A strong validator ("a3f9c2") means byte-for-byte identity; a weak validator (W/"a3f9c2") means “semantically equivalent, possibly with cosmetic differences.” Last-Modified (§8.8.2) is the coarser fallback: an HTTP-date timestamp of the last change, unreliable when its one-second granularity or a skewed server clock loses updates — which is exactly why ETags exist.

On revalidation the client echoes these back as preconditions:

  • If-None-Match: "a3f9c2" — “proceed only if the ETag does not match”; if it does match, the resource is unchanged and the server returns 304 Not Modified with no body, saving the transfer.
  • If-Modified-Since: <date> — the Last-Modified-based equivalent; 304 if unchanged.
  • If-Match / If-Unmodified-Since — the write-side guard: “apply my PUT/DELETE only if the resource is still in the state I last saw,” yielding 412 Precondition Failed on mismatch. This is the standard cure for the lost-update problem — two clients editing the same resource — and pairs with 428 Precondition Required (RFC 6585), by which a server can demand that writes be conditional.

RFC 9110 §13.2.2 fixes a strict evaluation order so preconditions never contradict: If-Match first, then If-Unmodified-Since (only if If-Match absent), then If-None-Match, then If-Modified-Since (only if If-None-Match absent), and If-Range last. If-None-Match takes precedence over If-Modified-Since because ETags are more reliable than timestamps.


Range Requests — Partial Transfers

Range requests (§14) let a client ask for a slice of a representation instead of the whole thing — the mechanism behind download resumption, video seeking, and parallel segmented downloads. A server advertises support with Accept-Ranges: bytes; a client that sees it can send Range: bytes=0-1023 for the first kilobyte, or Range: bytes=1024- for “everything from byte 1024 on,” or a multi-range Range: bytes=0-49, 100-149.

A satisfiable range yields 206 Partial Content with a Content-Range: bytes 0-1023/146515 header naming the slice and the total size; multi-range responses use a multipart/byteranges body. A range wholly outside the resource yields 416 Range Not Satisfiable. The critical companion is If-Range (§14.5): a client resuming a download sends If-Range: "a3f9c2" (or a date) alongside Range, meaning “if the resource is unchanged, send me just the missing bytes (206); if it changed, forget the range and send the whole new thing (200).” Without it, resuming a download whose source changed mid-transfer would silently stitch together bytes from two different files — a real, corrupting failure mode.


Failure Modes and Common Misunderstandings

“401 means forbidden.” No — 401 means unauthenticated (you haven’t proven who you are; a WWW-Authenticate challenge follows). 403 means authenticated-but-not-allowed; retrying with credentials to a 403 is pointless. Emitting 401 without WWW-Authenticate violates §15.5.2.

“POST is idempotent if my handler is careful.” Idempotency is a property RFC 9110 assigns to the method, and generic infrastructure (proxies, retry libraries) acts on that. A proxy will retry an idempotent GET/PUT after a timeout but will not retry a POST, because the spec forbids assuming POST is safe to repeat. If your POST needs safe retries, you must add an idempotency key — you cannot make the method itself idempotent.

“302 preserves my POST body.” Historically false and still a footgun. Browsers and clients routinely rewrote 301/302 POST→GET, dropping the body. Use 307 (temporary) or 308 (permanent) when the method and body must survive the redirect, and 303 deliberately when you want Post/Redirect/Get semantics.

Missing Vary behind a CDN. A content-negotiated response cached without Vary: Accept-Encoding will be served with the wrong encoding to some fraction of users — a classic “works for me, mojibake for them” cache-poisoning bug. See HTTP Caching and Content Delivery Networks and Edge Caching.

Weak vs strong ETag on range requests. RFC 9110 requires a strong validator for If-Range and for range revalidation; a weak ETag cannot guarantee byte-alignment across a partial transfer, so pairing W/"…" with Range is a spec violation that leads to corrupted resumed downloads.

Treating 404 and 410 as interchangeable. 404 says “not found (maybe never existed, maybe moved)”; 410 Gone says “this existed and is deliberately, permanently removed.” Search engines de-index a 410 far faster than a 404 — the distinction is operationally real.


See Also