# Content Negotiation

## 1. The Problem: One URL, Multiple Representations

The same resource — `/users/42` — might reasonably be served as JSON or XML, in English or Japanese, compressed with `gzip` or not at all. Content negotiation is the mechanism by which a client tells the server what it prefers and the server picks the best available match, all without needing a different URL per variant (`/users/42.json`, `/users/42.xml`, ... would work too, but couples the representation into the identifier itself).

## 2. `Accept`: Negotiating Media Type

```http
Accept: application/json, text/html;q=0.8, */*;q=0.1
```

Each entry is a media type (or a wildcard) with an optional `q` (quality) value from `0` to `1`, defaulting to `1` when omitted. Higher `q` means stronger preference. This example says: "JSON is what I really want; plain HTML is acceptable but I'd rather not; and if you have to, anything at all beats nothing."

```http
# Server picks JSON, and MUST say so explicitly
HTTP/1.1 200 OK
Content-Type: application/json
Vary: Accept
```

## 3. Specificity Beats `q` When Comparing Different Patterns

```http
Accept: text/*;q=0.9, text/html;q=0.8
```

This looks like `text/*` should win (higher `q`), but specificity is evaluated **before** `q` is used as a tiebreaker: an exact `type/subtype` match outranks a `type/*` wildcard, which outranks a bare `*/*`, regardless of the numeric `q` attached to each. `q` only breaks ties **within** the same specificity tier. So here, if the server can produce `text/html`, it should send `text/html` — the more specific pattern the client listed wins the match even though its stated `q` is numerically lower.

```
Specificity order (most to least specific):
  type/subtype   >   type/*   >   */*
```

## 4. `q=0` Means "Absolutely Not," Not "Neutral"

```http
Accept: application/xml;q=0, */*
```

A `q` of exactly `0` is a hard exclusion — "I can technically parse other things, but never send me XML." This is different from a media type being **absent** from the header, which just means "no stated preference either way, treated per the wildcard rules." Servers that only check "is `q` low" instead of "is `q` exactly zero" can accidentally serve an explicitly-rejected type.

## 5. `Accept-Language` and `Accept-Encoding`: the Same Pattern, Different Axis

```http
Accept-Language: en-US, en;q=0.9, fr;q=0.5
Accept-Encoding: br, gzip;q=0.8, *;q=0.1
```

Same `q`-weighted list syntax, different negotiation axis. `Accept-Language` uses language *ranges* (`en-US` is more specific than `en`, which is more specific than `*`), following the same specificity-before-`q` rule as section 3. `Accept-Encoding` negotiates compression and is covered from the caching side (including why `Vary: Accept-Encoding` matters for shared caches) in `networking-web/http/05-caching_and_compression` — this topic covers the negotiation algorithm itself; that one covers its caching consequences.

## 6. Server-Driven vs Agent-Driven Negotiation

**Server-driven** (the common case): the client sends `Accept*` headers, the server picks unilaterally and returns one representation. Simple, one round trip, but the server's algorithm might not match what the client would have actually chosen.

**Agent-driven**: the server returns a `300 Multiple Choices` (rare in practice) listing the available variants, and the client picks explicitly with a follow-up request. Almost never used in real APIs — the extra round trip defeats the point for most use cases — but it exists in the spec for cases where server-side heuristics genuinely can't guess correctly.

## 7. `Vary`: Telling Caches What Changed the Answer

```http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Language: en-US
Vary: Accept, Accept-Language
```

`Vary` lists every request header that influenced *this particular response*. A cache must treat requests with different values of a `Vary`-listed header as needing separate cache entries — a French `Accept-Language` request must never be served the cached English response, even for the identical URL. Omitting a header from `Vary` that actually affected the response is a real bug class: users intermittently get a response negotiated for someone else's request.

## 8. `406 Not Acceptable`

```http
HTTP/1.1 406 Not Acceptable
Content-Type: text/plain

No representation matching the requested Accept: application/xml is available.
```

Rarely used in practice — most real APIs ignore an unsatisfiable `Accept` and return their default representation anyway rather than failing the request outright, since a strict `406` breaks more clients than it helps (many HTTP clients send an `Accept` header they don't actually mean literally). It exists for cases where returning the wrong representation would be actively wrong (e.g. a strict XML-only integration).

![Client lists ranked preferences; server intersects them against what it can actually produce, using specificity first and q-value only as a tiebreaker](diagrams/01-negotiation-algorithm.svg)

## 9. Under the Hood

### The real matching algorithm is intersection, not just sorting the client's list

A naive implementation sorts the client's `Accept` entries by `q` and picks the first one the server "supports" by name. That's wrong whenever wildcards are involved, because the server also has to test **which of the representations it can actually produce** against **which client pattern matches each one**, then rank the matches — not rank the client's raw list in isolation. Concretely: given available representations `[application/json, text/html]` and `Accept: text/*;q=0.9, application/json;q=0.7`, the server must compute, for each representation, the *best matching pattern* from the client's list (`text/html` matches `text/*` at 0.9; `application/json` matches its exact entry at 0.7), and only then pick the representation with the highest resulting score — `text/html`, even though `application/json` was listed with an exact match and looks superficially more "specific" in the request. The specificity rule (section 3) applies per-representation, not globally across the whole header.

### Why `q=0` has to be checked as its own branch, not folded into a sort

If matching code just sorts candidates by score descending and takes the top one, a `q=0` entry that happens to be the *only* matching pattern for a representation will still let that representation through with a very low but non-negative score, silently violating the client's explicit refusal. Correct negotiation code treats `q=0` (or a representation matching **no** pattern at all when the header omits `*` implicitly disallowing anything unlisted) as elimination, applied before ranking survivors — a filter step, not just a sort key.

### `Vary` interacts with negotiation the same way it interacts with compression

Every representation a server *could* have chosen instead is exactly the set of things `Vary` needs to name. This is why `Vary` is naturally derived from "which request headers did my negotiation algorithm actually consult," rather than a value someone hand-picks once — if a server adds a new negotiated dimension (say, starts honoring `Accept-Charset`) and forgets to add it to `Vary`, caches will keep serving stale negotiated results indefinitely, exactly as described for compression in `networking-web/http/05-caching_and_compression`.

## 10. Common Pitfalls

1. **Sorting by `q` before applying specificity.** Produces wrong results whenever a wildcard has a higher `q` than an exact match that's also present — see section 3 and the Under the Hood algorithm.
2. **Treating `q=0` the same as "not mentioned."** `q=0` is an explicit veto; an unmentioned type falls back to wildcard rules (which may still allow it) — these are different outcomes.
3. **Forgetting `Vary` entirely, or listing only some of the headers actually consulted.** Both cause a cache to serve one user's negotiated response to another user expecting a different one.
4. **Trusting `Accept` headers literally when clients often send a broad, non-committal list.** Many real clients (browsers especially) send `Accept: */*` or a broad boilerplate list without meaning every entry seriously; defaulting to a sane representation on ambiguity is usually better than a strict `406`.
5. **Re-implementing `q`-value parsing with `parseFloat` and no bounds checking.** A malformed or out-of-range `q` (negative, `>1`, non-numeric) should degrade gracefully (commonly treated as `q=1` or the entry ignored), not throw or silently corrupt the ranking of other entries.
6. **Assuming content negotiation and CORS interact.** They don't — `Accept`/`Vary` negotiation happens regardless of origin; CORS is a separate, unrelated gate on whether the requesting page's JS may read the result at all (see `networking-web/browser-security/02-cors`).

## 11. Quick Reference

```
Accept: type/subtype;q=N, type/*;q=N, */*;q=N       -- q defaults to 1, range [0,1]
Specificity order (always applied BEFORE q as tiebreaker):
    type/subtype  >  type/*  >  */*

q=0            -> explicit "never send me this" -- NOT the same as unmentioned
Accept-Language -- same q-weighted syntax, language-range specificity (en-US > en > *)
Accept-Encoding -- same q-weighted syntax, see caching topic for Vary consequences

Server-driven negotiation -- server decides unilaterally (the common case, one round trip)
Agent-driven negotiation  -- server offers a 300 Multiple Choices list (rare in practice)

Vary: <headers>  -- MUST list every request header that changed this response;
                    a cache keys separately per distinct value of each listed header
406 Not Acceptable -- rarely used; most APIs serve a default instead of hard-failing
```
