Troubleshooting · Authentication

401 Unauthorized — it's usually not the key you just regenerated

30-SECOND VERSION
  1. Check which header the endpoint wants: ANTHROPIC_AUTH_TOKEN sends Authorization: Bearer, ANTHROPIC_API_KEY sends x-api-key — set the wrong one and a valid key still 401s.
  2. Run env | grep -i anthropic for an older value shadowing the new one, then restart the client (it reads the environment once, at startup).
  3. 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

any of these
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.

MessageWhat it tells youMost likely cause
invalid x-api-keyServer read an x-api-key header and rejected the valueWrong key — or the endpoint wanted a Bearer token
Invalid bearer tokenServer read Authorization: Bearer and rejected the valueWrong token — or the endpoint wanted x-api-key
Could not resolve authentication methodThe client gave up before sending anythingNeither variable set, or not the one this client reads
Bare 401, no bodySomething rejected it without explainingA proxy or gateway hop you didn't know was there

The two layers

Who authenticates to whomHow failure behavesWho can fix it
Layer 1Your client → the endpointFails immediately, on the first request, every timeYou
Layer 2The endpoint → its upstream providerOften intermittent, or starts suddenly with no config changeThe 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.

terminal
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.

VariableHeader the client sends
ANTHROPIC_API_KEYx-api-key: <value>
ANTHROPIC_AUTH_TOKENAuthorization: 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.

terminal
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.

terminal
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#

terminal
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 — 9Coding error responses carry one in the form (request id: 2026...). 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