TL;DR — Claude Code sends anthropic-beta headers for the experimental features it uses. Anthropic's own API accepts them; Bedrock, Vertex and most proxies do not, and reject the request before the model is reached. The trap is that the resulting 400 often names a parameter rather than the header, so people spend an afternoon on the request body when the header was the problem. Confirm by replaying the request without the beta headers; strip them at the proxy.
The symptom
Claude Code works against Anthropic directly. Point it at Bedrock, Vertex, or a gateway and every request 400s — frequently with a message about some field, occasionally about an unsupported beta feature, sometimes with no useful detail at all.
What makes this one expensive to debug: the error rarely says "header". You read a parameter name, you go looking at the body, and the body is fine.
Why it happens
Beta headers are how Anthropic ships features before they are generally available. Claude Code, tracking the API closely, opts into the ones it needs — the header is not something you configured, so it is not something you think to check.
A third-party endpoint has three options when it meets a beta header it does not implement, and which one you get is not up to you:
- reject the request — the 400 you are reading
- ignore the header and serve a degraded response — worse, because nothing fails and behaviour quietly differs
- implement it — eventually, on their schedule, not yours
Confirming it in one step
Replay a minimal request against your endpoint, first with the headers Claude Code sends, then without:
# With a beta header — mimics Claude Code
curl -sS $ANTHROPIC_BASE_URL/v1/messages \
-H "x-api-key: $KEY" -H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: some-beta-feature-2026-01-01" \
-H "content-type: application/json" \
-d '{"model":"'"$MODEL"'","max_tokens":16,"messages":[{"role":"user","content":"ping"}]}'
# Identical, minus the beta header
curl -sS $ANTHROPIC_BASE_URL/v1/messages \
-H "x-api-key: $KEY" -H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"'"$MODEL"'","max_tokens":16,"messages":[{"role":"user","content":"ping"}]}'
Second succeeds, first fails → it is the header, whatever the message claimed. Use the actual header value from your proxy's inbound log rather than a guess; which betas the client sends changes between versions.
Where to fix it
At the proxy — strip or allowlist. The only durable place. Drop anthropic-beta headers the upstream does not implement, rather than forwarding them and hoping.
Prefer an allowlist over a blocklist: forward the betas you have verified upstream supports, drop the rest. A blocklist silently breaks the day the client adopts a new beta, which is precisely the failure mode you are trying to end.
Not at the client. Claude Code does not expose which betas it sends; do not go looking for that setting.
The silent variant, which is worse
If your endpoint ignores unknown beta headers instead of rejecting them, nothing 400s — you simply get behaviour that differs from Claude Code against Anthropic, with no error anywhere. Symptoms are vague: a feature that seems not to work, output shaped differently than expected.
Worth knowing, because it means "no 400s" is not evidence that your beta handling is correct. If you suspect it, compare the same prompt against Anthropic directly and diff the responses.
Related
Beta headers are one member of a family — Claude Code speaking a dialect that only Anthropic's API fully accepts. The parameter-level version is Unknown parameter: output_config, and the tool-call version is tool_result / tool_use mismatch. If you hit one, check for the others — they share a cause.
Part of the LLM API Error Reference — errors indexed by their exact strings.