Content Negotiation Explained
Understand HTTP content negotiation, including media types, languages, compression, quality values, request headers, response headers, and practical API examples.
HTTP content negotiation allows a client and server to agree on the most appropriate representation of a resource. The same resource can often be represented in different formats, languages, or encodings, and the client can communicate which variants it prefers.
For example, an API might be able to return data as JSON or another media type, while a web page might be available in several languages. A client can express its preferences through request headers such as Accept, Accept-Language, and Accept-Encoding.
Content negotiation is an important part of HTTP because it separates the identity of a resource from the particular representation returned to the client. Understanding this distinction makes HTTP APIs, caching, internationalization, and compression easier to design correctly.
What Is Content Negotiation?
Content negotiation is the process by which a client communicates its preferred representation characteristics and a server selects a representation that can satisfy those preferences.
A resource can have multiple representations. For example, a document could be available as HTML, JSON, XML, or another format. The resource itself and its representations are related, but they are not necessarily the same thing.
GET /api/products/42
Accept: application/jsonThe Accept header tells the server that the client prefers a representation using the application/json media type.
If the server supports that representation, it can return JSON and identify the selected representation with the Content-Type response header.
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 42,
"name": "Example"
}Representation vs Resource
Content negotiation becomes easier to understand when distinguishing a resource from its representation. A resource is an abstract target identified by a URI. A representation is the data transferred to describe the current state of that resource in a particular format.
For example, /api/products/42 could identify a product resource. One client might request JSON while another client could request a different supported representation.
| Concept | Example |
|---|---|
| Resource | /api/products/42 |
| Representation | JSON representation of the product |
| Media type | application/json |
| Request preference | Accept: application/json |
| Response description | Content-Type: application/json |
The Accept Header
The Accept request header is one of the most important content negotiation headers. It tells the server which media types the client can understand or prefers to receive.
GET /api/products/42
Accept: application/jsonThe media type application/json indicates that the client wants a JSON representation.
A client can also list multiple acceptable media types:
Accept: application/json, text/htmlThe server can then choose an appropriate representation based on the available representations and the client's preferences.
The Wildcard Media Type
The asterisk can be used as a wildcard in the Accept header. It can indicate that the client accepts any media type within a particular category or any media type at all.
Accept: text/*
Accept: */*text/* means any media type in the text category, while */* is a general wildcard that matches media types broadly.
Quality Values and the q Parameter
Clients can assign relative preference to alternatives using the q parameter, also called a quality value.
Accept: text/html;q=1.0, application/json;q=0.8, */*;q=0.5In this example, text/html has the highest stated preference, followed by application/json, while the wildcard has a lower preference.
Quality values are preference weights rather than a guarantee that the server must return the highest-weighted representation. The server still has to consider which representations it actually supports and the rules used by its content negotiation implementation.
Understanding q=0
A quality value of zero indicates that the corresponding option is not acceptable to the client.
Accept: application/json, text/html;q=0This communicates a preference for JSON while explicitly excluding text/html.
The Content-Type Response Header
Content-Type is not the same as Accept. Accept is normally a request header expressing what representations the client can accept. Content-Type describes the media type of the representation contained in the message body.
GET /api/products/42
Accept: application/jsonHTTP/1.1 200 OK
Content-Type: application/json
{
"id": 42,
"name": "Example"
}For a response containing JSON, Content-Type tells the client how to interpret the response body.
Content Negotiation with Request Bodies
Content negotiation is often confused with the format of data sent by the client. When a request contains a body, Content-Type describes that request body.
POST /api/products
Content-Type: application/json
{
"name": "Keyboard"
}Here, Content-Type says that the request body is JSON. If the client also wants to tell the server what response format it prefers, it can use Accept separately.
POST /api/products
Content-Type: application/json
Accept: application/json
{
"name": "Keyboard"
}The two headers describe different directions: Content-Type describes the body being sent, while Accept describes a preferred representation for the response.
Accept-Language
Content negotiation is not limited to media types. Accept-Language allows a client to communicate preferred natural languages.
Accept-Language: en-US, en;q=0.9, de;q=0.7This indicates that the client prefers US English, can also use English more generally, and has a lower preference for German.
A multilingual web application can use this information when selecting an appropriate localized representation.
Language Tags
Language preferences use language tags such as en, en-US, fr, de, or ja. A more specific tag can identify a regional or language variant.
Accept-Language: fr-CA, fr;q=0.9, en;q=0.8The exact language selection process depends on the server and the available translations. A server should not assume that the first listed language is always available.
Accept-Encoding
Accept-Encoding is used for content coding negotiation. It tells the server which content codings the client can decode.
Accept-Encoding: gzip, brThe server can select a supported encoding and indicate the selected coding with the Content-Encoding response header.
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: brThe representation is still JSON, but its transferred content is encoded using Brotli.
Media Type vs Content Encoding
Media type and content encoding describe different properties of a response. Content-Type identifies the media type of the representation, while Content-Encoding identifies a content coding applied to that representation.
| Header | Purpose | Example |
|---|---|---|
| Content-Type | Identifies the media type | application/json |
| Content-Encoding | Identifies applied content coding | br |
| Accept | States acceptable media types | application/json |
| Accept-Encoding | States acceptable content codings | gzip, br |
Server-Driven Content Negotiation
The most common form of content negotiation is server-driven negotiation. The client sends preference information in request headers, and the server selects a representation.
GET /documents/42
Accept: application/json
Accept-Language: en-US
Accept-Encoding: br, gzipThe server can consider the requested media type, language, and encoding when constructing its response.
HTTP/1.1 200 OK
Content-Type: application/json
Content-Language: en-US
Content-Encoding: brThe response headers communicate which representation characteristics were selected.
The Vary Header
The Vary response header is important when content negotiation is involved. It tells caches which request headers influenced the selected representation.
HTTP/1.1 200 OK
Content-Type: application/json
Vary: AcceptIf the server can return different representations depending on Accept, a cache needs to know that Accept is relevant when deciding whether a stored response can be reused for another request.
For language and encoding negotiation, a response might use:
Vary: Accept, Accept-Language, Accept-EncodingWhy Vary Matters for APIs and CDNs
Modern applications often place caches or CDNs between clients and application servers. If the server chooses different representations based on request headers, those intermediaries need enough information to distinguish the variants.
For example, if one request receives JSON and another receives a different media type based on Accept, treating both responses as interchangeable could produce an incorrect response.
A Content Negotiation Example
GET /api/users/42
Accept: application/json, text/html;q=0.8
Accept-Language: en-US, en;q=0.9
Accept-Encoding: br, gzipThe client communicates three independent preferences: the desired media types, preferred languages, and acceptable content codings.
HTTP/1.1 200 OK
Content-Type: application/json
Content-Language: en-US
Content-Encoding: br
Vary: Accept, Accept-Language, Accept-EncodingThe server selected JSON, US English, and Brotli encoding. Vary communicates which request headers can affect the representation.
What Happens When No Representation Matches?
If a server cannot provide an acceptable representation for a request, it may respond with 406 Not Acceptable when that status is appropriate to the negotiation failure.
HTTP/1.1 406 Not Acceptable
Content-Type: application/json
{
"error": "No acceptable representation available"
}A 406 response communicates that the server could not produce a representation acceptable according to the request's stated preferences.
406 Not Acceptable vs 415 Unsupported Media Type
These status codes are often confused because both relate to representation formats, but they address different situations.
| Status | Typical meaning |
|---|---|
| 406 Not Acceptable | The server cannot provide a representation that satisfies the client's requested preferences. |
| 415 Unsupported Media Type | The server does not support the media type or content coding of the request content in the relevant context. |
For example, a client might send a request body using an unsupported media type and receive 415. A client requesting only representations that the server cannot provide may receive 406.
Content Negotiation in REST APIs
REST APIs can use content negotiation when an endpoint supports multiple representations. For example, an API might support JSON for browser and application clients while also exposing another representation for a specialized consumer.
GET /api/products/42
Accept: application/jsonA different client could request another supported representation:
GET /api/products/42
Accept: application/xmlThe same resource can therefore have multiple representations without requiring completely different resource identities.
Content Negotiation and API Design
Not every API needs to support multiple representations. If an API is intentionally JSON-only, it can document application/json as its representation format and keep negotiation simple.
- Support only the representations that provide a real use case.
- Document supported media types.
- Use Content-Type correctly for request and response bodies.
- Use Accept when clients can choose between representations.
- Return appropriate status codes for unsupported representations.
- Consider cache behavior when representations vary.
Content Negotiation and Browsers
Browsers automatically send several headers that can participate in content negotiation. The exact values depend on the browser, user configuration, request context, and supported features.
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, brA browser can therefore communicate that it accepts HTML and other representations, has language preferences, and supports particular content codings.
Content Negotiation and Internationalization
Language negotiation can be useful when building multilingual applications. A server can use Accept-Language as one signal when selecting a default language.
However, applications should not necessarily make language selection depend exclusively on the browser's preference. Users may explicitly choose a language through application settings, and that explicit preference can be more useful for subsequent requests.
Content Negotiation and Compression
Compression is another common form of HTTP negotiation. A client advertises supported content codings through Accept-Encoding, and the server can select one that is supported and appropriate.
Accept-Encoding: br, gzipIf Brotli is selected, the response can contain:
Content-Encoding: brThis does not change the underlying media type. A JSON response remains application/json even when its transferred representation is compressed.
Content Negotiation vs URL-Based Formats
Applications can sometimes select representations through URLs, query parameters, or file extensions instead of HTTP content negotiation.
/products/42.json
/products/42.xmlAnother design can use the same resource URI and let Accept determine the requested representation:
GET /products/42
Accept: application/jsonBoth approaches can be used in real systems. The appropriate design depends on the API architecture, client requirements, routing model, caching strategy, and compatibility constraints.
Proactive vs Reactive Negotiation
HTTP content negotiation is often described in terms of proactive and reactive approaches. In proactive negotiation, the server selects a representation based on request information such as Accept or Accept-Language.
Reactive negotiation allows the server to provide information that helps the client choose another available representation. The client can then make another request for the selected representation.
In everyday web APIs, server-driven or proactive negotiation is the pattern developers encounter most frequently.
Content Negotiation and Caching Problems
Negotiated responses can create multiple variants of the same resource. This can complicate caching because a cache needs to know which request characteristics influence the selected representation.
For example, if an endpoint returns English or German depending on Accept-Language, the cache cannot safely treat the English and German responses as interchangeable.
Vary: Accept-LanguageThe Vary header provides the information needed for this distinction.
Content Negotiation and ETags
Different representations of the same resource can have different entity tags. A JSON representation and another representation can therefore require different validators.
When caching negotiated responses, developers should consider both representation selection and cache validation. The representation returned for a request must correspond to the relevant request preferences and validator semantics.
Common Content Negotiation Mistakes
Mistake 1: Confusing Accept and Content-Type
Accept describes acceptable response representations. Content-Type describes the media type of the message body. They are related but not interchangeable.
Mistake 2: Ignoring Accept-Encoding
Applications and infrastructure that support compression need to account for the encodings a client can decode and correctly describe the selected encoding with Content-Encoding.
Mistake 3: Forgetting Vary
If a response varies according to request headers and the cache is not informed about those headers, an intermediary can potentially reuse the wrong representation.
Mistake 4: Treating q Values as Absolute Commands
Quality values express preferences. They do not force the server to provide a representation it does not support.
Mistake 5: Supporting Too Many Representations
Adding multiple formats can increase implementation, testing, documentation, and caching complexity. Representation negotiation should solve an actual compatibility or product requirement.
Testing Content Negotiation
Content negotiation can be tested by sending the same resource request with different Accept, Accept-Language, and Accept-Encoding values and comparing the responses.
GET /api/products/42
Accept: application/jsonGET /api/products/42
Accept: application/xmlGET /api/products/42
Accept-Language: de
Accept-Encoding: gzip- Check the response Content-Type.
- Check Content-Language when language negotiation is used.
- Check Content-Encoding when compression is negotiated.
- Check Vary when the representation depends on request headers.
- Test unsupported preferences and expected error handling.
- Test different q values when the server supports multiple alternatives.
A Practical API Example
Suppose an API exposes a document resource at /api/documents/15. The server supports JSON and XML, and the application supports English and German translations.
GET /api/documents/15
Accept: application/json, application/xml;q=0.8
Accept-Language: de, en;q=0.8
Accept-Encoding: br, gzipThe server could select JSON, German, and Brotli if all three are supported.
HTTP/1.1 200 OK
Content-Type: application/json
Content-Language: de
Content-Encoding: br
Vary: Accept, Accept-Language, Accept-EncodingThe response headers make the selected representation characteristics explicit and help intermediaries understand which request headers can affect the response.
Content Negotiation Checklist
- Define which representations each endpoint supports.
- Use Accept when clients need to express response format preferences.
- Use Content-Type to describe request and response bodies.
- Use Accept-Language when language negotiation is appropriate.
- Use Accept-Encoding and Content-Encoding for content coding negotiation.
- Use quality values when clients need to express relative preferences.
- Return 406 when no acceptable representation can be provided and that response is appropriate.
- Return 415 when the request content uses an unsupported media type in the relevant context.
- Use Vary when request headers affect representation selection and caching.
- Test negotiated responses through browsers, API clients, and intermediaries where relevant.
Helpful HTTP Tools
Several types of developer tools can make content negotiation easier to inspect and troubleshoot. Content-Type lookup tools help identify media types, MIME type detectors can verify file-related formats, and HTTP header tools make request and response headers easier to inspect.
- Content-Type lookup tools for checking media type values.
- MIME type detectors for identifying likely media types.
- HTTP header generators for creating test request headers.
- HTTP header viewers for inspecting request and response metadata.
- HTTP response formatters for making API responses easier to inspect.
Frequently Asked Questions
What is HTTP content negotiation?
Content negotiation is the process of selecting an appropriate representation of a resource based on information supplied by the client and the representations supported by the server.
What does the Accept header do?
Accept tells the server which media types the client can accept or prefers for the response, such as application/json or text/html.
What is the difference between Accept and Content-Type?
Accept describes acceptable response media types, while Content-Type describes the media type of the message body being sent.
What does Accept-Language do?
Accept-Language communicates the client's preferred natural languages, allowing a server to select an appropriate localized representation when supported.
What does Accept-Encoding do?
Accept-Encoding tells the server which content codings the client can decode, such as gzip or Brotli. The selected coding is normally identified by Content-Encoding in the response.
Why is Vary important for content negotiation?
Vary tells caches which request headers influenced the selected representation. This helps prevent an intermediary from incorrectly reusing one representation for requests with different preferences.
What is a 406 Not Acceptable response?
A 406 response can indicate that the server cannot provide a representation that satisfies the client's stated preferences.
Does every API need content negotiation?
No. An API that intentionally supports one representation, such as JSON, can keep its behavior simple. Negotiation becomes useful when clients need to choose between multiple representations or other representation characteristics.
Conclusion
HTTP content negotiation allows clients and servers to work with different representations of the same resource. The client can communicate preferences through headers such as Accept, Accept-Language, and Accept-Encoding, while the server describes the selected representation with headers such as Content-Type, Content-Language, and Content-Encoding.
The distinction between Accept and Content-Type is especially important: Accept describes what the client wants to receive, while Content-Type describes the media type of the message body. Quality values allow clients to express relative preferences, and Vary helps caches correctly handle responses whose representations depend on request headers.
Content negotiation is not required for every API, but when multiple representations, languages, or content codings are supported, understanding these HTTP mechanisms helps create APIs that are predictable, interoperable, and easier to cache and debug.