Views: 121

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 curl or an HTTP library.
  • The request works the moment you change or drop the Accept header.
  • You added Accept: application/json (or application/xml) and it started failing.
  • A GET returns 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

  1. An Accept header requesting a format the endpoint doesn’t produce - The classic. Your client sends Accept: application/json to a route that only renders HTML, or Accept: application/xml to 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.

  2. Strict framework content negotiation - The behavior depends entirely on the framework. Ruby on Rails raises ActionController::UnknownFormat and answers 406 when a respond_to block doesn’t handle the requested format — including when the URL carries a format extension like /report.pdf that no format.pdf clause exists for. Spring returns HttpMediaTypeNotAcceptableException (406) when a controller’s produces= list can’t satisfy the Accept header. Express’s res.format() sends 406 when no branch matches and you didn’t define a default. Same status code, framework-specific triggers.

  3. 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 Accept in 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-restrictive Accept-Encoding that 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 Accept and usually comes back 200 — and that split is the whole diagnosis. If the route answers normally to a permissive request but 406 to your narrow Accept, 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 Accept header your client is actually sending. curl -v or the browser network tab shows the real value. Nine times out of ten it’s a narrow application/json or application/xml where the route produces something else.
  • Try the request with Accept: */* (or no Accept at 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 .xml or .pdf can trigger 406 on a route that doesn’t render that format.
  • Check the other negotiation headers, not just Accept. An over-restrictive Accept-Charset, Accept-Language, or Accept-Encoding can 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 Accept header, 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

[Ad] Guide Detail Inline
← Back to All Guides