{
    "slug": "content_negotiation",
    "term": "HTTP Content Negotiation",
    "category": "api_design",
    "difficulty": "intermediate",
    "short": "The HTTP mechanism by which clients declare what formats, languages, and encodings they accept (Accept, Accept-Language, Accept-Encoding) and servers respond with the best match — or 406 Not Acceptable if none fits.",
    "long": "Content negotiation allows a single URL to serve multiple representations of the same resource. Clients send preference headers: Accept (MIME type), Accept-Language (human language), Accept-Encoding (compression), and Accept-Charset. Servers parse these using quality values (q=0.9) to select the best match and respond with Content-Type, Content-Language, and Content-Encoding headers confirming what was sent. Proactive (server-driven) negotiation is the most common: the server picks based on the Accept header. Reactive negotiation returns a 300 Multiple Choices response listing options. In PHP APIs, content negotiation enables one endpoint to serve JSON, XML, or MessagePack based on the client request, and to respond in the user's preferred language without separate URLs. The Vary header must be set to tell caches that responses differ by the negotiated dimension.",
    "aliases": [],
    "tags": [
        "http",
        "api",
        "rest",
        "headers",
        "internationalisation"
    ],
    "misconception": "Content negotiation does not require separate URLs per format — the same endpoint serves JSON, XML, or HTML based on Accept headers, with Vary telling caches they are different representations.",
    "why_it_matters": "Proper content negotiation keeps APIs clean (no /resource.json vs /resource.xml URLs) and enables transparent internationalisation without query parameters — the language is a header concern, not a routing one.",
    "common_mistakes": [
        "Forgetting to set Vary: Accept — caches serve the first-seen format to all subsequent clients regardless of their Accept header.",
        "Ignoring quality values (q=) in Accept headers — a client sending Accept: text/html, application/json;q=0.9 prefers HTML.",
        "Returning 200 with a non-matching Content-Type instead of 406 — silent format mismatch causes parsing errors on the client."
    ],
    "when_to_use": [
        "Use Accept-based negotiation when an API endpoint legitimately needs to serve multiple formats (JSON, XML, CSV) from the same URL.",
        "Use Accept-Language negotiation for internationalised API responses where the language is request-scoped, not user-preference-scoped.",
        "Always set the Vary header to match whichever Accept-* dimensions your responses vary on."
    ],
    "avoid_when": [
        "Avoid content negotiation for format selection if your API has a single dominant consumer — a ?format=json query parameter is simpler and more debuggable.",
        "Do not use content negotiation for API versioning — it couples version semantics to the Accept header and makes routing opaque."
    ],
    "related": [
        "api_design",
        "http_caching",
        "cors"
    ],
    "prerequisites": [],
    "refs": [
        "https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation"
    ],
    "bad_code": "// Ignoring Accept header — always returns JSON, sets no Vary:\npublic function show(int $id): Response {\n    $data = $this->repo->find($id);\n    return response()->json($data); // XML client gets JSON, cache serves JSON to everyone\n}",
    "good_code": "// Negotiate format, set Vary:\npublic function show(Request $req, int $id): Response {\n    $data = $this->repo->find($id);\n    $accept = $req->getAcceptableContentTypes();\n    if (in_array('application/xml', $accept)) {\n        return response()->xml($data)->header('Vary', 'Accept');\n    }\n    return response()->json($data)->header('Vary', 'Accept');\n}",
    "example_note": "The bad example ignores the Accept header and omits Vary — an XML client silently receives JSON, and a cache serves that JSON response to all subsequent clients. The fix negotiates the format and sets Vary: Accept so caches store separate representations.",
    "created": "2026-03-31",
    "updated": "2026-03-31",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/content_negotiation",
        "html_url": "https://codeclaritylab.com/glossary/content_negotiation",
        "json_url": "https://codeclaritylab.com/glossary/content_negotiation.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[HTTP Content Negotiation](https://codeclaritylab.com/glossary/content_negotiation) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/content_negotiation"
            }
        }
    }
}