HTTP Content Negotiation is the mechanism by which a client and server agree on the best representation of a resource to return, based on client preferences and server capabilities. It allows a single URL to serve different formats, languages, encodings, or character sets without requiring separate endpoints.
Content negotiation is a built-in HTTP feature defined in RFC 7231 that lets a client declare what kinds of responses it can accept, and lets the server choose the most appropriate representation. For example, the same URL might return JSON for an API client and HTML for a browser. This keeps APIs clean and RESTful by avoiding format-specific URLs like /users.json or /users.html.
Clients send special request headers to communicate their preferences. The four primary negotiation headers are Accept (media type), Accept-Language (human language), Accept-Encoding (compression algorithm), and Accept-Charset (character encoding). Each header can list multiple values with optional quality factors (q-values) ranging from 0 to 1, where a higher value means stronger preference — for example, Accept: application/json;q=1.0, text/html;q=0.8.
The server inspects the client's preference headers, compares them against the representations it can produce, and selects the best match using a weighted ranking algorithm. It then returns the chosen representation along with a Content-Type header (and often Content-Language or Content-Encoding) describing what was actually sent. If the server cannot satisfy any of the client's preferences, it should return a 406 Not Acceptable status code.
Proactive (server-driven) negotiation means the server makes the choice autonomously based on the request headers — this is by far the most common approach. Reactive (agent-driven) negotiation means the server returns a 300 Multiple Choices response listing available representations and lets the client pick one. Proactive negotiation is simpler and faster, while reactive negotiation gives the client ultimate control but adds an extra round-trip.
When a server performs content negotiation, it must include a Vary response header listing the request headers that influenced its decision — for example, Vary: Accept, Accept-Language. This tells caches (like CDNs or browsers) that responses to the same URL can differ based on those headers, preventing a cached French HTML page from being served to an English JSON client. Omitting Vary is one of the most common and damaging bugs in content-negotiation implementations.
Always validate and sanitize Accept headers server-side, since malformed q-values or unsupported media types can cause unexpected behavior. Prefer explicit Content-Type headers in responses rather than relying on defaults, and document which representations your API supports. For APIs, returning application/problem+json (RFC 7807) for error responses regardless of the requested format is a widely adopted convention that improves debuggability.
© RM Full Stack & AI Engineer · All guides · Roadmaps · Open the app