# Content Negotiation

> Content negotiation lets one URL serve different formats, languages or encodings based on Accept headers. See q-values, Vary, 406 and the caching traps.

Source: https://howhttpworks.com/glossary/content-negotiation
Last reviewed: 2026-10-04

> **TL;DR:** Content negotiation is one URL, several representations. The client states preferences with `Accept`, `Accept-Language` and `Accept-Encoding`; the server picks one and must list the headers it used in `Vary`.

Content negotiation is the HTTP mechanism for choosing among multiple representations of the same resource. In the common server-driven form, the client sends preference headers and the server selects a format, language or encoding, then labels the choice with `Content-Type`, `Content-Language` or `Content-Encoding`.

## An example

```http
GET /reports/42 HTTP/1.1
Host: api.example.com
Accept: application/json, text/csv;q=0.5, */*;q=0.1
Accept-Language: de-CH, de;q=0.9, en;q=0.5
Accept-Encoding: br, gzip
```

```http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Language: de
Content-Encoding: br
Vary: Accept, Accept-Language, Accept-Encoding
```

## How the choice is made

- Each header lists options with optional quality values from 0 to 1. No `q` means 1; `q=0` means "never".
- More specific ranges beat less specific ones regardless of order. `text/html;level=1` overrides `text/html`, which overrides `text/*`, which overrides `*/*`.
- `Accept-Encoding` is the one negotiation that every site does, and compression is applied after the representation is chosen.

## Non-obvious facts

- **`Vary` is not optional.** A CDN or browser that caches a negotiated response without `Vary` will serve the wrong variant to the next user. The reverse, `Vary: User-Agent` or `Vary: Cookie`, fragments the cache into near-useless pieces; normalize the header at the edge or negotiate on something narrower.
- **406 is rarely what you want.** RFC 9110 allows the server to send a default representation instead of a 406, and most do. Browsers send `*/*` anyway, so a 406 mostly appears on API endpoints with a strict `Accept`.
- **A file extension or query parameter is often better for caches.** `/reports/42.csv` is cacheable, linkable and easy to test with `curl`, while `Accept` negotiation is invisible in the URL.
- **Languages are a bad place for surprises.** Redirecting by `Accept-Language` alone breaks sharing and crawling; many sites offer a language switcher and treat the header as a hint.
- **Request bodies have the mirror image.** A client's `Content-Type` that the server does not accept yields `415 Unsupported Media Type`, not 406.

## Go deeper

- [Accept header](https://howhttpworks.com/headers/accept)
- [Accept-Language header](https://howhttpworks.com/headers/accept-language)
- [Accept-Encoding header](https://howhttpworks.com/headers/accept-encoding)
- [Vary header](https://howhttpworks.com/headers/vary)
- [406 Not Acceptable](https://howhttpworks.com/status-codes/406)
- [Content negotiation guide](https://howhttpworks.com/guides/content-negotiation): how servers pick a representation, real server examples, and CDN caching.
