DEV Community

Jeff
Jeff

Posted on Originally published at powerduck.com

The 406 Not Acceptable: Your Accept Header Named a Format the API Doesn't Return

Quick answer: A 406 Not Acceptable means the server cannot produce a response that matches the Accept header your client sent. It is not an auth problem and not a malformed body. The request was understood, but the content negotiation failed because the client asked for a representation the resource does not offer. Fix the Accept header, or remove it so the server can return its default format.

Content negotiation in one minute

HTTP content negotiation lets a client declare what media types it can handle. The Accept header carries a list of types, each with an optional quality value, and the server picks the best match. A 406 is the server's way of saying none of the offered types overlap with what it can return. This is easy to confuse with a 415 Unsupported Media Type, but the two are mirror images.

Status Header involved Direction Meaning
406 Not Acceptable Accept What the client wants back Server cannot return a matching type
415 Unsupported Media Type Content-Type What the client is sending Server cannot parse the request body

A concrete reproduction

Imagine an endpoint that serves reports in JSON and PDF. A generated client, configured for an older service, sends a narrow header asking only for XML.

If the report resource has no XML representation, the server returns 406 even though the report exists and the credentials are valid. The response often includes an Accept header listing the types actually available, which is the fastest clue you will get.

HTTP/1.1 406 Not Acceptable Accept: application/json, application/pdf Vary: Accept Content-Type: application/problem+json

Why an AI agent can trigger this without noticing

Agents frequently copy a header from an unrelated example or hardcode a default Accept value when building a client. Because the header works fine against most endpoints, the bug only surfaces on a resource that enforces strict negotiation. Agents also tend to focus on the request body and the Content-Type header, treating Accept as boilerplate, so they do not check it when debugging. If you hand the agent a failing request, explicitly ask it to inspect every request header rather than only the body.

How to debug it methodically

  1. Re-run the request with curl -v and read the outgoing Accept header. Confirm exactly what the client sent.
  2. Remove the Accept header entirely and retry. If the request now succeeds, negotiation was the problem.
  3. Send a permissive header such as Accept: */* and confirm a 200. This proves the resource is reachable.
  4. Read the response Accept header on the 406 and choose one of the listed types.
  5. Pin the correct type in the client and add a regression test for that resource.

# Works: ask for a type the server actually offers curl -i https://api.example.com/v1/reports/r-91 \ -H "Accept: application/json" # Also works: let the server choose its default curl -i https://api.example.com/v1/reports/r-91

Quality values and wildcard matching

An Accept header can express preferences rather than hard requirements. A value of application/json;q=0.9, */*;q=0.1 says "prefer JSON, but I will accept anything else." This is usually safer than a single rigid type because it gives the server a fallback. Reserve a strict, single-type header for cases where your client genuinely cannot parse anything else. If you build on top of an OpenAPI description, the responses section already tells you which media types the operation can produce, so the client should be generated from that list rather than guessed.

Fixing it at the client layer

For a JavaScript client, set the header per request or through a default that is permissive enough not to break strict endpoints.

Make sure the fix is not a global Accept: */* that papers over a resource that genuinely needs special handling, such as a binary download. The right header reflects what the calling code is prepared to parse.

The Vary header and caching

When responses differ based on the Accept header, the server should send Vary: Accept so caches keep the JSON and PDF representations separate instead of serving one client's format to another. If you debug a 406 that appears only intermittently, check whether a shared cache is serving a previously stored representation without re-running negotiation. Understanding the Vary header also explains why the same URL can return different content types legitimately; the resource is the same, but the representation is chosen per request.

Frequently asked questions

Can a 406 be caused by something other than the Accept header?

It is driven by content negotiation, which can also involve Accept-Charset, Accept-Encoding, or Accept-Language, but in modern APIs the Accept media type is the overwhelming cause. Start there.

Why does the same request work in the browser but fail in my client?

Browsers send a broad, permissive Accept header and let the server choose. A hand-written client often sends a narrow one copied from an example. Compare the two sets of outgoing headers with the network panel or curl -v.

Is returning 406 for a missing query parameter correct?

No. A bad or missing parameter is a 400 Bad Request. A 406 specifically means the server cannot satisfy the requested representation. If a server returns 406 for validation errors, that is a misclassification worth flagging to the API maintainer.

Should I just always send Accept: /?

It avoids the error but loses the contract that helps the server and your code agree on the format. Prefer the specific type your code will parse, with a wildcard fallback at lower quality, and derive it from the OpenAPI responses when possible.

This is part of the The API Debugging Guide — start there for a systematic way to read status codes and request headers instead of guessing.

Top comments (0)