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.

Related Protocols

Related Glossary Terms

More in HTTP Fundamentals