Scrapeless Wiki

What Is Content Negotiation? – Definition and How It Works

Informational P3 what is content negotiation

Learn how Accept headers select a representation, why Vary matters for caching, and how negotiation affects collected data.

Content negotiation is the mechanism by which a client and server agree on which representation of a resource to return β€” format, language, encoding β€” using request headers rather than distinct URLs.

1. The Request Headers

Header Selects
Accept Media type β€” HTML, JSON, XML
Accept-Language Natural language
Accept-Encoding Compression
Accept-Charset Character encoding (largely obsolete)

Each accepts a weighted list:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-GB,en;q=0.9,fr;q=0.5

The q values are preferences from 0 to 1. Absent, q=1 is assumed.

2. The Same URL, Different Answers

This is the property that matters most for collection: one URL can return different content depending on headers.

curl -H "Accept: text/html"        https://api.example.com/items/42   # an HTML page
curl -H "Accept: application/json" https://api.example.com/items/42   # JSON

Where a site offers JSON for the same URL as its HTML page, requesting it is strictly better than parsing markup β€” no selectors, no layout coupling, typed values. It is worth probing for on any API-backed site.

3. Vary Tells Caches What Mattered

A server that negotiates must declare which headers affected the response:

Vary: Accept, Accept-Language, Accept-Encoding

Without it, a cache stores one representation and serves it to everyone β€” the classic bug where a user gets another user's language, or a JSON client receives cached HTML.

For a client with its own cache, Vary is the instruction on what belongs in the cache key. Ignoring it produces exactly the same bug locally.

4. Language Negotiation Changes Your Data

Accept-Language is the header most often overlooked, and it silently changes collected values: month names, decimal separators, thousands separators, currency formatting, date order, and the entire text of a page.

A collection run that does not pin Accept-Language can produce a dataset whose number formats vary by whatever the default was β€” which is the sort of inconsistency discovered much later, in a parser.

Pin it explicitly, and record it alongside the data.

5. Negotiation Is Not the Only Mechanism

Sites also select representations by:

  • Distinct URLs β€” /en/page and /fr/page, or .json and .html suffixes.
  • Query parameters β€” ?format=json.
  • IP geolocation β€” which is not negotiation at all, and cannot be overridden by headers.
  • Cookies β€” a stored language preference beating the header.

The geolocation case is the one that defeats header configuration: if the site decides by egress IP, only the egress location changes the answer.

6. Practical Guidance

  1. Probe for JSON. Send Accept: application/json against a page URL; when it works, it eliminates HTML parsing entirely.
  2. Pin Accept-Language for every run, and store the value with the results.
  3. Keep Accept-Encoding honest β€” advertise only what you can decode, and match the browser you claim to be.
  4. Respect Vary in any cache key you build.
  5. Record the negotiated response headers, so a later change of representation is visible rather than mysterious.
  6. Check for a cookie or IP mechanism when headers appear to have no effect.

7. Summary

Content negotiation lets one URL serve multiple representations, selected by Accept, Accept-Language, and Accept-Encoding, with Vary telling caches which headers mattered.

For collection it cuts both ways: requesting JSON where a site offers it removes HTML parsing altogether, while an unpinned Accept-Language silently varies number formats, dates, and text β€” and where a site decides by IP instead, no header will change the answer.