What Is Content Negotiation?
Content negotiation is the HTTP mechanism that lets a client declare what representations it can understand, and lets the server pick the best match. Instead of hardcoding a URL per format (e.g. /data.json vs /data.xml), a single URL serves different representations based on request headers.
This keeps APIs clean — one canonical URL, multiple representations.
Server-Driven vs Agent-Driven Negotiation
Server-Driven (Proactive) Negotiation
The server inspects the client's Accept-* headers and picks the best representation. This is by far the most common approach.
GET /api/product/42 HTTP/1.1
Accept: application/json, application/xml;q=0.8, */*;q=0.5
Accept-Language: en-US, en;q=0.9, fr;q=0.5
Accept-Encoding: gzip, br
The server reads these headers and responds with the most appropriate content type, language, and encoding.
Agent-Driven (Reactive) Negotiation
If the server cannot pick automatically, it returns 300 Multiple Choices with a list of available representations. The client then requests the one it wants. This is rare in practice.
The Accept Header Family
`Accept` — Media Type
Specifies the MIME types the client accepts, with optional quality values (q factors, 0.0–1.0, default 1.0):
Accept: application/json
Accept: text/html, application/json;q=0.9, */*;q=0.5
The server picks the highest-quality media type it can produce. */* acts as a wildcard catch-all.
`Accept-Language` — Natural Language
Specifies preferred human languages:
Accept-Language: en-US,en;q=0.9,fr;q=0.7
Servers use this for i18n — returning content in the preferred language or falling back gracefully.
`Accept-Encoding` — Compression
Specifies compression algorithms the client supports:
Accept-Encoding: gzip, deflate, br, zstd
gzip— Ubiquitous, ~65–70% compressionbr(Brotli) — ~15% better than gzip, HTTPS-only in browserszstd(Zstandard) — Fastest at similar ratios, increasingly supported
`Accept-Charset` — Deprecated
This header was once used to negotiate character sets (e.g., UTF-8 vs ISO-8859-1). It is now deprecated — UTF-8 is assumed universally. Do not send or rely on Accept-Charset.
Relevant Status Codes
| Code | Meaning |
|---|---|
| **200 OK** | Server found an acceptable representation |
| **300 Multiple Choices** | Multiple representations exist; client must choose |
| **406 Not Acceptable** | No representation matches the Accept headers |
When you return 406, include an error body listing available media types so the client can correct its request.
The `Vary` Header
When responses differ based on request headers, the server must include Vary to tell caches what determines uniqueness:
Vary: Accept, Accept-Language, Accept-Encoding
Without Vary, a CDN might serve a French user the cached English response.
Best Practices for API Content Negotiation
1. Always Set `Content-Type` in Responses
Even if your API only produces JSON, always send:
Content-Type: application/json; charset=utf-8
2. Use `application/problem+json` for Errors (RFC 9457)
Error responses should have their own media type — don't send a Content-Type: application/json body that describes an error:
Content-Type: application/problem+json
3. Prefer Explicit `q` Values
When a client sends Accept: */*, the server should default to its primary format (usually JSON for APIs) rather than guessing.
4. Ignore Unknown `Accept-*` Headers Gracefully
If you cannot honor an Accept-Language preference, fall back to your default language rather than returning 406. Reserve 406 for cases where the media type truly cannot be satisfied.
5. Test with `curl`
# Request JSON explicitly
curl -H "Accept: application/json" https://api.example.com/products/1
# Request with language preference
curl -H "Accept-Language: fr-FR" https://example.com/about
# Check what the server actually returned
curl -I https://api.example.com/products/1
Summary
Content negotiation keeps your API URLs canonical while supporting multiple representations. Use Accept for format, Accept-Language for locale, Accept-Encoding for compression. Always include Vary in responses that differ by header, and return 406 only when no representation is truly possible.