401 Unauthorized — it's usually not the key you just regenerated
- Check which header the endpoint wants: ANTHROPIC_AUTH_TOKEN sends
Authorization: Bearer, ANTHROPIC_API_KEY sendsx-api-key— set the wrong one and a valid key still 401s. - Run
env | grep -i anthropicfor an older value shadowing the new one, then restart the client (it reads the environment once, at startup). - Repeat the request with curl: curl works, client 401s = the client isn't reading your variable; curl 401s too = credential or endpoint.
When a client talks to a compatible endpoint there are two separate authentication layers, and a 401 can come from either one. The error text looks identical whichever layer rejected it. Regenerating your own key only addresses one of them — and in practice it's the less common one.
What you're seeing
API Error: 401 {"type":"error","error":{"type":"authentication_error","message":"invalid x-api-key"}}
401 Unauthorized
Could not resolve authentication method. Expected either apiKey or authToken to be set.
{"error":{"message":"Invalid bearer token","type":"authentication_error"}}
All four mean the request arrived without credentials the server accepted. None of them says which layer rejected it — but the wording narrows it down.
| Message | What it tells you | Most likely cause |
|---|---|---|
invalid x-api-key | Server read an x-api-key header and rejected the value | Wrong key — or the endpoint wanted a Bearer token |
Invalid bearer token | Server read Authorization: Bearer and rejected the value | Wrong token — or the endpoint wanted x-api-key |
Invalid token (request id: …) | The 9Coding gateway did not receive a valid key | Key missing, truncated, or not a 9Coding key |
Could not resolve authentication method | The client gave up before sending anything | Neither variable set, or not the one this client reads |
Bare 401, no body | Something rejected it without explaining | A proxy or gateway hop you didn't know was there |
The two layers
| Who authenticates to whom | How failure behaves | Who can fix it | |
|---|---|---|---|
| Layer 1 | Your client → the endpoint | Fails immediately, on the first request, every time | You |
| Layer 2 | The endpoint → its upstream provider | Often intermittent, or starts suddenly with no config change | The endpoint operator |
Most write-ups only cover layer 1 — "check your key." That advice is fine, but it cannot explain the case that actually confuses people: it worked an hour ago and you changed nothing. When that happens, layer 1 is not where your problem is.
Check in this order
1 · Is the credential actually reaching the request?#
Copy-paste carries invisible passengers — trailing newlines, non-breaking spaces, smart quotes from a chat window.
printf '%s' "$ANTHROPIC_AUTH_TOKEN" | wc -c
printf '%s' "$ANTHROPIC_AUTH_TOKEN" | tail -c 20 | xxd | tail -2
A byte count one higher than the visible length means a trailing newline came along for the ride.
2 · Are you setting the variable that produces the header the endpoint expects?#
This is the quietest failure of the four, because nothing in the message hints at it.
| Variable | Header the client sends |
|---|---|
ANTHROPIC_API_KEY | x-api-key: <value> |
ANTHROPIC_AUTH_TOKEN | Authorization: Bearer <value> |
If the endpoint authenticates on Authorization: Bearer and you set ANTHROPIC_API_KEY, the request arrives with no Authorization header at all. The server answers 401 with a message about an invalid key. The key is fine; the header is wrong. The reverse happens too.
With 9Coding, set ANTHROPIC_AUTH_TOKEN — the key begins with sk-9c-.
3 · Does your base URL end where the client expects it to?#
Clients append their own path (/v1/messages, /v1/chat/completions) to the base URL you give them. Set the base URL with /v1 already on the end and the request goes to /v1/v1/messages. That usually returns 404 — but a gateway that authenticates before routing can return 401 instead.
echo "$ANTHROPIC_BASE_URL"
The correct value is exactly https://api.9coding.com — no trailing slash, no /v1.
4 · Is an older value shadowing the one you just set?#
A value exported in a shell profile, a value in a project config file, and a value in the current shell are three different things. The client reads one of them; you may be editing another.
env | grep -i anthropic
Check every hit, not just the one you remember setting. Then restart the client — Claude Code reads environment variables once at startup, so a long-running process keeps the environment it began with.
5 · Take the client out of the picture#
curl -s -w '\n%{http_code}\n' https://api.9coding.com/v1/models \
-H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN"
- curl returns 200, client returns 401 → the client isn't reading the variable you think it is. Back to step 4.
- curl returns 401 too → the credential or the endpoint is the problem, not the client.
- curl fails before returning a status → this isn't an auth problem. See connection errors.
When it isn't your problem
If all of these are true, you're looking at layer 2 and there is nothing to fix on your machine:
- it worked earlier today,
- you changed no configuration,
- other clients pointed at the same endpoint fail the same way,
- and it recovers on its own after a while.
Check the operator's status page and changelog before spending another hour on your own setup. An endpoint that publishes neither is an endpoint you cannot debug — worth knowing in itself. Ours is at status.
When reporting it, include the request id: when 9Coding itself rejects a request, message ends with (request id: 2026...); if the error has no request id, first check whether it came from 9Coding at all (see Error source). That id plus the timestamp, the model name and the full error text lets the exact call be traced. A report that only says it doesn't work cannot be investigated.
401 or 403 — not the same problem
A 403 means the request was authenticated and then refused: the account lacks access to that model, that region, or that feature. Rotating the key is guaranteed not to help. See 403 Forbidden.
Related
- 403 Forbidden — authenticated but not permitted
- TLS certificate errors — why they fail on the first try and never retry
- Connection errors — separating network, proxy, and endpoint
- Error source — when the error did not come from 9Coding (such as Missing x-bb-api-key header)
- All Claude Code errors — the quick reference table