HTTP Fundamentals

HTTP Content Negotiation: Complete Guide

How clients and servers negotiate the best representation of a resource using Accept headers — format, language, encoding, and beyond.

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% compression
  • br (Brotli) — ~15% better than gzip, HTTPS-only in browsers
  • zstd (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

CodeMeaning
**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.

Связанные протоколы

Связанные термины глоссария

Больше в HTTP Fundamentals