Content Negotiation
A single resource can have several representations — JSON, CSV, different languages. Content negotiation lets the client state a preference and the server pick the best match, all without changing the URI.
How it works
Section titled “How it works”The client sends Accept (which media types it wants) and optionally Accept-Language; the server replies with the chosen representation and a matching Content-Type:
GET /reports/42 HTTP/1.1Accept: text/csv, application/json;q=0.8Accept-Language: th, en;q=0.5The q values are quality weights — here the client prefers CSV, then JSON. The server should honor the best type it supports and echo it in Content-Type.
When it cannot comply
Section titled “When it cannot comply”- If the server cannot produce any accepted type, respond
406 Not Acceptable. - If a request body arrives in a type the server cannot parse, respond
415 Unsupported Media Type.
Negotiating in a running API
Section titled “Negotiating in a running API”| Advantages (Content Negotiation) | Trade-offs |
|---|---|
| One API supports multiple formats (JSON, XML, CSV) | The server has to implement multiple serializers |
| The client says exactly which format it wants | More complexity — content-type routing logic |
| Media-type versioning is the most RESTful option | Little tooling support for custom media types |
Compression via Accept-Encoding cuts bandwidth | Cache keys must account for the Accept header |
Common mistakes
Section titled “Common mistakes”Not returning Content-Type in the response
Symptom:
- The server returns JSON but never sets
Content-Type: application/json - The client doesn’t know how to parse the body — some treat it as plain text
- Always set
Content-Typeon every response that has a body
Caching without including Accept in the cache key
Symptom:
- A CDN caches the response to
GET /reportsrequested as JSON - The next request asks for CSV — the CDN returns the cached JSON instead
- Set a
Vary: Acceptheader so the CDN includesAcceptin the cache key
💡 Real-world examples
GitHub API:
Accept: application/vnd.github+jsonfor standard JSONAccept: application/vnd.github.raw+jsonfor raw file content- Different formats via the
Acceptheader, no URI change neededAWS API:
Accept-Encoding: gzip— the response body is compressed automatically- Cuts bandwidth for large JSON responses like list operations