406 Not Acceptable: Fix the Accept Header Mismatch
406 Not Acceptable means the server can't produce a response matching your Accept header. Tell it from 415, then fix the header in 4 checks. Free instant check, no sign-up.
Check your domain for this issue now
Free, no sign-up. Runs the exact check this guide describes and shows what to fix.
Problem
A request comes back 406 Not Acceptable. The URL is right, the resource exists, and it loads fine in a browser — but your API client, your curl command, or your server-to-server call gets 406. The server is telling you it has no representation of this resource that matches the format you asked for in your Accept header.
Symptoms
- The same URL returns 200 in a browser but 406 from
curlor an HTTP library. - The request works the moment you change or drop the
Acceptheader. - You added
Accept: application/json(orapplication/xml) and it started failing. - A
GETreturns 406 while write methods behave normally — the opposite pattern from a 415.
What 406 Actually Means
RFC 9110 (§15.5.7) defines 406 as the target resource not having “a current representation that would be acceptable to the user agent, according to the proactive negotiation header fields,” and the server being “unwilling to supply a default representation.” Two things have to be true at once: your Accept (or Accept-Charset, Accept-Language, Accept-Encoding) rules out everything the server can produce, and the server refuses to fall back to a default. Both conditions matter, and the second is why 406 is rarer than you’d expect.
This is the exact mirror of a 415 Unsupported Media Type. A 415 is the server saying it can’t consume the Content-Type of your request body. A 406 is the server saying it can’t produce a response matching your Accept. One is about what you sent; the other is about what you want back. People mix them up because both are about media types, but they sit on opposite ends of the request — and knowing which end you’re on tells you which header to fix.
Here’s the part that trips people up: RFC 9110 makes honoring Accept a SHOULD, not a MUST. A server is free to ignore your Accept header and return its default format anyway. Most do — which is precisely why 406 is uncommon. When a server bothers to send 406 instead of just giving you its default, it means someone configured strict content negotiation, and your request genuinely asked for something off the menu.
Top 3 Causes
-
An Accept header requesting a format the endpoint doesn’t produce - The classic. Your client sends
Accept: application/jsonto a route that only renders HTML, orAccept: application/xmlto a JSON-only API. The server runs content negotiation, finds nothing in its available representations that matches, and returns 406. This is by far the most common cause, and it’s almost always the client demanding a format the server was never built to emit on that path. -
Strict framework content negotiation - The behavior depends entirely on the framework. Ruby on Rails raises
ActionController::UnknownFormatand answers 406 when arespond_toblock doesn’t handle the requested format — including when the URL carries a format extension like/report.pdfthat noformat.pdfclause exists for. Spring returnsHttpMediaTypeNotAcceptableException(406) when a controller’sproduces=list can’t satisfy theAcceptheader. Express’sres.format()sends 406 when no branch matches and you didn’t define adefault. Same status code, framework-specific triggers. -
A middlebox or a malformed Accept header - If content negotiation clearly shouldn’t be involved and you still get 406, suspect what’s between you and the origin, or the header itself. A proxy, CDN, or WAF can inject or rewrite
Acceptin transit, or a security rule can return 406 as its block response. A client library can also emit a broken header — a bad quality value (application/json;q=), a typo in the subtype, or an over-restrictiveAccept-Encodingthat rules out what the server compresses with. The header looks fine to you and is nonsense to the server.
Diagnose with DechoNet
- HTTP Check to see how the endpoint responds to a clean, permissive request. A neutral check sends a browser-like
Acceptand usually comes back 200 — and that split is the whole diagnosis. If the route answers normally to a permissive request but 406 to your narrowAccept, the problem is your header versus what the endpoint produces, not a broken deployment. If it returns 406 even to a clean request, look upstream at a proxy, CDN, or WAF rewriting headers or blocking the request.
Resolution Checklist
- Print the
Acceptheader your client is actually sending.curl -vor the browser network tab shows the real value. Nine times out of ten it’s a narrowapplication/jsonorapplication/xmlwhere the route produces something else. - Try the request with
Accept: */*(or noAcceptat all). If it succeeds, you’ve confirmed the mismatch is your header, and the fix is to ask for a format the endpoint actually serves. - Match the media type to what the API documents it returns. If it’s JSON-only, send
Accept: application/json; if it’s HTML, stop forcing JSON. Also check the URL — a format extension like.xmlor.pdfcan trigger 406 on a route that doesn’t render that format. - Check the other negotiation headers, not just
Accept. An over-restrictiveAccept-Charset,Accept-Language, orAccept-Encodingcan also produce 406 if the server can’t satisfy it and won’t default. - Confirm it isn’t a 415 in disguise. If the failure is about the body you’re sending rather than the response you’re requesting, you want 415, not 406 — different header, different fix.
- Re-run and confirm the endpoint returns 2xx.
When to Escalate
- If a request that worked yesterday returns 406 today with no client change, suspect a proxy, CDN, or WAF that started rewriting or stripping the
Acceptheader, or a security rule returning 406 as a block. An HTTP Check that shows the origin healthy to a direct request but 406 through the edge points straight at the middlebox. - If you own the API and legitimate clients are hitting 406, reconsider strict negotiation. RFC 9110 lets you return a sensible default instead of 406, and for most APIs that’s the friendlier behavior — a client that asked for XML on a JSON service is usually better served a clear JSON error than a bare “Not Acceptable” with no body.
Related Tools
Related Guides
Share this guide