A single URL can exist in several forms — the same document as HTML or JSON, in Korean or English, gzip-compressed or not. Content negotiation is the HTTP mechanism by which client and server agree on which of these 'representations' to exchange. The client advertises its preferences with Accept-family request headers, and the server picks the best-matching representation it has.
The Accept-family headers
The client's preferences are split across axes, each negotiated independently.
Accept: Desired media types. e.g.Accept: application/json, text/html;q=0.9.Accept-Language: Desired natural languages. e.g.Accept-Language: ko-KR, ko;q=0.9, en;q=0.8.Accept-Encoding: Acceptable compression schemes. e.g.Accept-Encoding: br, gzip.Accept-Charset: Character encoding (rarely used today since almost everything is UTF-8).
Ranking with q-values
The q parameter (quality value) appended to each candidate is a preference weight between 0 and 1. Omitted, q defaults to 1.0 (top preference); q=0 is an explicit 'do not send me this.' The server reads these weights and returns the highest-scoring representation it can produce. The request below means 'I most want Korean (1.0), English is fine if not (0.8), and any other language (0.5) is a last resort.'
GET /article/42 HTTP/1.1
Host: example.com
Accept: text/html, application/json;q=0.8
Accept-Language: ko;q=1.0, en;q=0.8, *;q=0.5
Accept-Encoding: br, gzip;q=0.9
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Language: ko
Content-Encoding: br
Vary: Accept, Accept-Language, Accept-Encoding
Server-driven vs agent-driven negotiation
There are two styles. Server-driven negotiation, as above, has the server inspect the request headers and immediately answer with the representation it deems best. It's the common case because it's a single round trip; its weakness is that the server can never perfectly know the client's true preference. Agent-driven negotiation has the server return a list of choices (e.g. 300 Multiple Choices) and the client re-request one of them. It's precise but costs an extra round trip, so it's rare in practice — the explicit alternative of encoding the format into the URL itself (/article/42.json, /ko/article/42) is far more widely used.
Vary: keep caches from confusing representations
Content negotiation entangles dangerously with caching. Since one URL yields different responses per client, a cache that stores the Korean response and serves it to an English user causes a bug. The server must therefore signal, via the Vary response header, that 'this response depends on the listed request headers.' With Vary: Accept-Language, a cache keeps a separate copy per language. Forgetting to list a negotiated header in Vary produces cache poisoning, where the wrong variant is handed to another user.
406 Not Acceptable
When none of the server's representations can satisfy the client's Accept constraints, the code is 406 Not Acceptable. If a client insists on Accept: application/xml but the server has only JSON, it may answer 406. In practice, though, many APIs skip strict 406 and simply return their default representation (usually JSON) — the reasoning being that a best-effort representation beats an empty error for the user. Whichever you choose, document the behavior in your API reference.