When a client's HTTP Accept header cannot be satisfied, a server may return 406 Not Acceptable or ignore the header and send a default representation. Which do real servers do, and why?
answer
- RFC 9110 allows both — it is a design choice
- real Accept headers are wrong or rewritten
- 406 body must itself pick a media type
- strict versioning = the real 406 case
- instrument 406s; a spike means a proxy changed
basics
~20 sMost servers ignore an unsatisfiable Accept header and send their default representation, because a 406 is usually less useful than a body the client can inspect and because Accept headers are frequently wrong or over-narrow. 406 is reserved for genuinely strict negotiation.
solid answer
~50 sRFC 9110 explicitly permits both: a server that cannot satisfy `Accept` may send 406, or it may **disregard the header and respond anyway**, on the grounds that the user may still find the result usable. In practice, serving the default wins, for good reasons: - Real `Accept` headers are often wrong — libraries hard-code narrow values, proxies rewrite the header, and browsers send lists that do not include your type. - A 406 with an error body is self-contradicting: you are sending a media type the client just said it would not accept. - A client that receives an unexpected `Content-Type` can at least log it, inspect it, or fail with a real diagnostic; a 406 gives it nothing. Where 406 is right: strict versioned APIs where silently returning a different variant would corrupt the client, and image/format negotiation where you can genuinely list alternatives. Whatever you choose, be consistent, document it, and always report the actual type in `Content-Type` so clients can detect the substitution.
code
http · 8 linesGET /users/42 HTTP/1.1
Accept: application/xml
HTTP/1.1 200 OK
Content-Type: application/json
Vary: Accept
{"id":42,"name":"Ada"}go deeper
Know that a server is allowed to ignore Accept and send its default, and that most do.
Explain why: unreliable Accept headers, the self-contradicting error body, and better diagnostics — plus always setting an accurate Content-Type.
Give the decision rule (strict for versioned or format-critical contracts, fallback otherwise), your framework's default behaviour, and the 406-rate instrumentation that catches proxy rewrites.
Make it an API-wide, documented policy with consistent semantics, client-robustness guidance, and monitoring, framing it as an error-budget and supportability tradeoff rather than a per-endpoint whim.
## What the specification allows RFC 9110's definition of 406 includes an unusual escape hatch. A server that cannot supply an acceptable representation may return 406 — *or* it may ignore the `Accept` header entirely and respond with whatever it has, because "the user agent may be able to make use of it" and "a user might prefer a response with an unacceptable content coding to no response at all". So both behaviours are conformant. The question is which is more useful, and that is a design judgement. ## Why serving the default usually wins **Accept headers are unreliable in the wild.** - HTTP client libraries hard-code values: many send `Accept: */*`, some send a stale hard-coded `application/json`, some send nothing. - Corporate proxies, security appliances and API gateways rewrite or strip request headers. - Old SDK versions ask for media types you retired. If you enforce strictly, every one of these becomes a 406 for a request you could have answered. **A 406 body is self-contradicting.** To explain the failure you must choose a media type — but the premise of the 406 is that you have none the client accepts. Serving `application/problem+json` to a client that said `Accept: application/xml` is exactly the substitution you just refused to make. **Diagnostics.** A developer who gets JSON when they asked for XML sees the real data and the real `Content-Type`, and immediately understands. A developer who gets an empty 406 has to reason backwards through the whole chain to work out which header caused it — especially hard when an intermediary rewrote it. **Client robustness is the durable rule anyway.** Well-written clients check the response `Content-Type` before parsing. Once that is true, sending the default is safe and the strictness bought nothing. ## Where 406 is genuinely right - **Strictly versioned APIs.** If a client asks for `application/vnd.example+json;version=3` and you only have v2, silently returning v2 can corrupt data or crash the client. Failing loudly is the safer contract. - **Format-critical resources.** A client that can only decode WebP truly cannot use an AVIF. Refusing, with a list of alternatives, is honest. - **Machine-to-machine contracts** you control on both ends, where a wrong representation is worse than an error and every client is guaranteed to set the header deliberately. Even then, a 406 should say what *is* available — a list of supported types in the body, using the most broadly-parseable type you have. ## Frameworks and the accidental 406 Most web frameworks default to strict negotiation: if no configured message writer matches `Accept`, they emit 406. This produces the very common production incident where a client works for months and then starts receiving 406 because a proxy began appending or rewriting `Accept`, or because a serialiser for a media type was removed in a refactor. Knowing your framework's default — and whether it can be configured to fall back to a default representation — is the practically useful part of this topic. ## The decision, stated crisply Whichever you choose: 1. **Be consistent across the API.** Half the endpoints 406-ing and half falling back is worse than either policy. 2. **Document it.** Clients need to know whether an unexpected `Content-Type` is possible. 3. **Always set `Content-Type` accurately.** Falling back is only safe because the client can detect it. 4. **Send `Vary: Accept`** if the response genuinely depends on the header, so caches stay correct. 5. **Instrument it.** Count 406s (or count fallbacks) per client and per route. A spike is a signal that an intermediary changed the header or that an SDK version is asking for something you removed — you want that on a dashboard, not in a support ticket. ## A note on Accept-Encoding The same latitude exists for content codings: if a client's `Accept-Encoding` cannot be satisfied, a server may send an unencoded (identity) response rather than fail. In practice servers always do — identity is universally decodable, so refusing would be absurd. The asymmetry is instructive: fall back when a universally-safe representation exists, refuse when it does not.
- What is awkward about the body of a 406 response?You have to serialise the explanation in some media type, but the whole reason for the 406 is that you have nothing the client said it accepts. Any body you send is the substitution you just refused to make. Practically, servers use application/problem+json or text/plain and accept the irony.
- A long-stable endpoint suddenly starts returning 406 to some clients. What changed?Almost always something rewrote or narrowed the Accept header — a new proxy, gateway or security appliance in the path, or an SDK upgrade that now requests a specific media type. The other common cause is a serialiser or message converter being removed on the server, so the framework's strict negotiation no longer finds a writer.
- Does the same fallback latitude apply to Accept-Encoding?Yes, and servers universally take it: if no offered coding is supported, they send the identity (uncompressed) representation rather than failing. It is safe precisely because identity is decodable by everyone — which shows the underlying rule, fall back when a universally-usable representation exists.
A translator asked for Latvian who only has English can either say nothing at all, or hand over the English and let you decide — most of the time the English is worth more than silence.
saying these in an interview costs you the question
- Insisting the specification requires 406 whenever Accept cannot be matched.
- Falling back to a default but leaving Content-Type inaccurate or unset.
- Applying one policy on some endpoints and the opposite on others without documenting it.
- Treating an unexpected Content-Type as impossible in client code.
- Never instrumenting 406 rates, so an intermediary rewriting Accept surfaces only as user complaints.