This error tells you the call failed and almost nothing about why. The useful information — the status code and the provider's message — is one debug flag away, and each status points at a different fix.
The error
API Error: bad_response_status_code
(no status, no provider message — the wrapper hides both)Causes and fixes at a glance
| Cause | Fix |
|---|---|
| 401 / 403 underneath | Credential or header mismatch against a custom base URL. Check which auth variable is set. |
| 404 underneath | Either the model id is unknown to that host, or the base URL has an extra path segment. |
| 402 underneath | Gateway wallet is empty. Top up; nothing about the client configuration is wrong. |
| 429 / 529 underneath | Rate limited or upstream saturated. Retry with backoff rather than reconfiguring. |
| A non-JSON body with a 200 | A captive portal, corporate proxy or error page. The status may be fine and the body still unusable. |
Turn the wrapper into a real error
Claude Code's debug output prints the request and the upstream response. Run one failing call with it on and read the status line — everything after this step depends on what it says.
claude --debug 2>&1 | tee claude-debug.log
grep -iE 'status|http/|error' claude-debug.log | head -20Reproduce the same call with curl
Take the base URL and credential out of the tool and issue the request directly. This separates "the host rejects us" from "the client is malformed" in one step — and the raw body usually names the problem in plain language the wrapper discarded.
curl -i "$ANTHROPIC_BASE_URL/v1/messages" \
-H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
-H "content-type: application/json" \
-d '{"model":"claude-sonnet-4-6","max_tokens":16,
"messages":[{"role":"user","content":"ping"}]}'Check the base URL has no trailing path
Claude Code appends its own `/v1/...` path. A base URL that already ends in `/v1` produces `/v1/v1/messages`, which any host answers with a 404 — wrapped, once again, as bad_response_status_code. Set the origin only.
# Wrong — doubles the version segment
export ANTHROPIC_BASE_URL="https://api.kunavo.com/v1"
# Right — origin only
export ANTHROPIC_BASE_URL="https://api.kunavo.com"If you’re calling through Kunavo
Against Kunavo the two statuses worth recognising immediately are 402 and 401: 402 means the wallet cannot cover the request — balance, not configuration — and 401 almost always means the credential landed in ANTHROPIC_API_KEY instead of ANTHROPIC_AUTH_TOKEN, since Kunavo reads a bearer token. Both come back with a JSON body naming the reason, so the debug log is decisive rather than suggestive. Requests that fail are not billed. Which variable to set, and why, is covered in the auth-variable guide.
FAQ
Is this error ever Claude Code's own bug?
Rarely. It is a transport-level wrapper: something answered, and the answer was not a success. Reproducing with curl settles it — if curl also fails, the client is not the problem.
It works with the official API but not my gateway.
Then the difference is the credential or the base URL, not the tool. Check the auth header the gateway expects and whether your base URL already contains /v1.
Should I retry automatically?
Only after you know the status. Retrying a 401 or 404 is pointless; retrying a 429 or 529 with backoff is correct.
Related guides
- Claude Code “API Error: 401 authentication_error” with a custom base URL — every cause
- ANTHROPIC_AUTH_TOKEN vs ANTHROPIC_API_KEY — which one Claude Code actually reads
- Claude API “credit balance is too low” / 402 insufficient_quota — the fix
More error semantics live in the error reference; getting a key takes a minute via sign up and the authentication docs.