Headers and representations

Content negotiation

See how Accept headers let a client express preferences and how a server selects an available representation of a resource.

One URL can have more than one representation. The same resource might be available as HTML, JSON, or a compressed variant. The client states its preferences with Accept headers:

Accept: text/html, application/json;q=0.8
Accept-Language: en, it;q=0.7
Accept-Encoding: br, gzip

The server picks a supported option. Quality values such as q=0.8 rank one format below another. Accept-Encoding tells the server which compression the client understands.

Let’s try it. Ask for JSON first:

curl -I -H 'Accept: application/json' https://flaviocopes.com/

This site returns HTML regardless, because it does not negotiate format on that URL. An API endpoint would behave differently:

curl -I -H 'Accept: application/json' https://httpbin.org/json

You should get content-type: application/json. The server matched your preference.

The server is not required to support every requested representation. An API may always return JSON. A site may use distinct URLs like /en/docs and /it/docs instead of negotiating language on one path.

When a response changes according to a request header, caches need to know. A response header such as Vary: Accept-Encoding tells a cache to keep separate copies for different compression preferences. Without Vary, a cache might serve a gzip body to a client that asked for uncompressed content.

Be careful when debugging negotiation. curl sends different default Accept headers than a browser. Compare both in the Network panel and in the terminal to see why you get different Content-Type or Content-Encoding values.

Language negotiation works the same way. A browser might send Accept-Language: en-US,en;q=0.9,it;q=0.8. The server picks the best match from the formats it supports, or falls back to a default locale. Distinct URLs per language sidestep negotiation entirely, which many content sites prefer because URLs stay shareable and cache-friendly.

Try this: send two curl requests to the same API URL, one with Accept: application/json and one with Accept: text/html. Note whether the server returns different status codes, bodies, or Content-Type values.

Quick check

Result

You got of right.

Lesson completed