Content Negotiation

How clients and servers agree on the best format for a response

The Negotiation Mechanism

Content negotiation is the HTTP mechanism that lets a client and server agree on the best format for the response. The client uses Accept headers to say what it wants; the server picks the best match from what it can provide.

This is fundamental to the web's design: the same resource (identified by the same URL) can have multiple representations. A book at /api/books/42 might be available as JSON, HTML, or XML. The client asks for its preferred format, and the server obliges if it can.

The Accept Headers

Header Negotiates Example
Accept Content type (format) Accept: text/html, application/json
Accept-Language Language Accept-Language: en-US, fr;q=0.5
Accept-Encoding Compression Accept-Encoding: gzip, br
Accept-Charset Character encoding (rarely used — UTF-8 dominates) Accept-Charset: utf-8

Quality Values (q parameter)

Clients can express preferences using the q parameter (quality value), ranging from 0 (not acceptable) to 1 (most preferred, the default):

Accept: text/html, application/json;q=0.9, text/plain;q=0.5

# This means:
#   text/html        → q=1.0 (most preferred, default when q is omitted)
#   application/json → q=0.9 (acceptable, slightly less preferred)
#   text/plain       → q=0.5 (acceptable but not great)

The server reads these preferences and picks the highest-quality match from what it can provide. If the server can't provide any of the requested formats, it responds with 406 Not Acceptable.

Content Negotiation in Action

── Request ─────────────────────────────────────────
GET /api/books/42 HTTP/1.1
Host: example.com
Accept: application/json;q=1.0, text/html;q=0.8

── Response (server chooses JSON) ───────────────────
HTTP/1.1 200 OK
Content-Type: application/json
Vary: Accept

{"id": 42, "title": "HTTP Guide", "author": "Jane Doe"}

The client preferred JSON (q=1.0) over HTML (q=0.8). The server could provide both, so it chose JSON. If another client sends Accept: text/html, the same URL could return an HTML page instead.

The Vary Header

When a server returns different content based on request headers (like Accept), it must include a Vary header in the response. This tells caches: "the response depends on these request headers — don't serve a cached JSON response to a client that wants HTML."

Vary: Accept              # Response varies by content type
Vary: Accept-Encoding     # Response varies by compression
Vary: Accept, Accept-Language  # Response varies by both

Without Vary, a cache might serve the wrong representation. If the first client requests JSON and the response gets cached, the next client asking for HTML would incorrectly receive the cached JSON. The Vary header prevents this by making the cache key include the specified request headers.