The header that decides the response shape before any code runs
Content negotiation is how a single URL can serve different representations of the same resource — JSON to one client, HTML to a browser navigating directly, XML to a legacy integration — based entirely on what the client says it can accept, via the Accept request header. Most JSON APIs only ever serve one representation, which is exactly why this header is easy to ignore until something depends on it.
The basic mechanism
curl -H "Accept: application/json" https://api.example.com/users/42
curl -H "Accept: text/html" https://api.example.com/users/42A server that honors content negotiation inspects Accept, picks a representation it can produce that the client claims to understand, and reflects that choice back in the response's Content-Type header — which is a *different* header, describing what was actually sent, not what was requested. A client sending Accept: application/json but receiving Content-Type: text/html back (a login redirect page, an error page from a proxy) is precisely the failure mode that turns into a confusing "Unexpected token <" JSON parse error — the client tried to JSON.parse an HTML page because the server ignored its Accept header, or something in front of the API (a load balancer, an auth proxy) intercepted the request before it ever reached the real handler.
Vendor-specific media types
A plain application/json accept header says "I understand generic JSON." APIs that need finer-grained content negotiation define their own vendor media type, often folding an API version into it:
curl -H "Accept: application/vnd.github+json" https://api.github.com/users/octocat
curl -H "Accept: application/vnd.api+json" https://api.example.com/orders/42 # JSON:API's own media type
curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/users/octocat # version embeddedThis is the mechanism behind header-based API versioning — the version lives inside the media type string itself, and a server can dispatch to entirely different serialization logic based on which vendor type was requested, all while the URL stays identical. JSON:API formalizes its own vendor type (application/vnd.api+json) specifically so a client can assert "I understand the JSON:API document structure," not just "I understand JSON in general."
Quality values: expressing a preference, not a requirement
Accept supports weighted preferences via q values, letting a client rank multiple acceptable formats in one header instead of picking exactly one:
Accept: application/json, application/xml;q=0.8, text/html;q=0.5This says "JSON preferred (default weight 1.0), XML acceptable as a fallback, HTML as a last resort" — a server should pick the highest-q option it's actually capable of producing. In practice, most JSON-only APIs skip implementing full weighted negotiation and simply check "does the Accept header contain application/json or */*, and if not, return 406 Not Acceptable" — which covers the common case without the complexity of a full negotiation algorithm.
Accept vs. Content-Type: the two headers people mix up
| Header | Direction | Meaning |
|---|---|---|
Accept | Request | "Here's what representations I can understand" |
Content-Type | Request (with a body) | "The body I'm sending you is encoded like *this*" |
Content-Type | Response | "The body I'm sending back is encoded like *this*" |
A POST request needs *both*, for different reasons: Content-Type: application/json on the request tells the server how to parse the request body you're sending; Accept: application/json tells it what format to send the *response* back in. Missing Content-Type on a JSON POST body is one of the most common causes of a server treating the payload as unparseable — and returning a 415 Unsupported Media Type error, a distinct failure mode from an Accept-header mismatch.