Blog

Anthropic API 401 "invalid x-api-key": What It Means and How to Fix It

Short answer

A 401 response with "type": "authentication_error" and the message invalid x-api-key means the Claude API rejected the credential in the request's x-api-key header. Anthropic's error reference says a 401 means there is an issue with your API key, for example that it's malformed, revoked or expired. Retrying with the same key won't change the result.

Often the key you created is fine and a different value is sent in its place. Check these three things in order:

  1. What was sent. A stale ANTHROPIC_API_KEY, a placeholder, or a ${VAR} that was never filled in.
  2. Where it was sent. A gateway key sent to api.anthropic.com is rejected, and so is an Anthropic key sent to a gateway that issues its own keys.
  3. Whether the key still works. Disabled, deleted and expired keys stop working. Check the API keys page in the Claude Console.

What the error looks like

The raw response from api.anthropic.com, as quoted in anomalyco/opencode#47114 (request ID shortened):

json
{"type":"error","error":{"type":"authentication_error","message":"invalid x-api-key"},"request_id":"req_..."}

Tools wrap it in their own text. Two examples from public bug reports:

text
401 {"type":"error","error":{"type":"authentication_error","message":"invalid x-api-key"}}
text
Warning: Error 401: authentication_error - invalid x-api-key

The first is from Gentleman-Programming/gentle-shell#681, the second from rocketride-org/rocketride-server#1105. In the official Python SDK, a 401 raises anthropic.AuthenticationError.

When a valid key stops working

Anthropic's authentication docs list these ways a key stops working:

Situation What happens
The key is disabled in the Console Requests fail until someone clicks Re-enable
The key is deleted Permanent. The key is archived and can't be restored
The key has expired Requests return 401 authentication_error. Expired keys can't be reactivated; create a new one
A personal key's owner leaves the organization The key is archived. Being re-invited doesn't bring it back
A workspace key (legacy) loses its workspace The key stops working when the workspace is archived

Expiration is set when the key is created: 3 hours, 1 day, 7 days, 30 days, a custom duration, or Never if your organization allows it. Anthropic emails the key's creator before keys with a lifetime of 7 days or more expire. Shorter-lived keys expire without a warning email. The API keys table in the Console shows each key's expiration date.

When the key looks right but still fails

These are the patterns behind most public reports of this error:

  • Another value shadows your key. In gentle-shell#681, the tool preferred an environment variable over its own saved credential, and a placeholder ANTHROPIC_API_KEY inherited from the parent process won. Unsetting the variable fixed it. The official Python SDK also reads ANTHROPIC_API_KEY from the environment when you don't pass api_key.
  • A placeholder was sent as-is. In rocketride-server#1105, a key field set to ${ROCKETRIDE_ANTHROPIC_KEY} was sent literally instead of being replaced with the value from .env.
  • The request went to Anthropic instead of your gateway. In opencode#47114, a configured proxy base URL was lost after a plugin reload. The proxy's key went to api.anthropic.com and came back as invalid x-api-key. The reporter also flagged the security side: a key meant for a private relay was sent to a host the user never configured.
  • A subscription token was sent as an API key. In openclaw/openclaw#178387, a credential from claude setup-token worked for chat, but a PDF tool sent it in x-api-key and got this 401. The analysis in that issue is that this kind of credential has to go in Authorization: Bearer. Console API keys start with sk-ant-api.
  • The key was copied incompletely. A missing character, a trailing newline or a space from a chat message or a document makes the key malformed.

How to fix it

  1. Look at what your code actually sends. Print a masked version of the key and the base URL from the same process that makes the request:
python
import os

key = os.environ.get("ANTHROPIC_API_KEY", "")
print(len(key), repr(key[:10]), repr(key[-4:]))
print(os.environ.get("ANTHROPIC_BASE_URL"))

repr shows a trailing \n or space. A key that doesn't start with sk-ant-api isn't an Anthropic Console API key.

  1. Test the key on its own. Bypass your app and call the API directly with the same key:
bash
curl https://api.anthropic.com/v1/models \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01"

If this returns a model list, the key is valid and the problem is in how your app loads or routes it. If it returns the same 401, fix the key itself.

  1. Check the key in the Console. On the API keys page, check its status and expiration. If it was deleted or has expired, create a new key and replace the old value everywhere it is stored, including .env files, CI secrets and secrets managers.
  2. Remove stale values. Run env | grep ANTHROPIC (PowerShell: Get-ChildItem Env:ANTHROPIC*) in the shell that starts your app. Remove old exports from shell profiles, and check that .env placeholders are filled in at runtime.
  3. Check the host. If you use a gateway, make sure every code path uses its base URL. The Python SDK reads ANTHROPIC_BASE_URL, but other tools may only read their own setting. A key from one service won't work on another.
  4. Match the header to the credential. Console API keys work in x-api-key. Anthropic's docs now recommend Authorization: Bearer <key> and keep x-api-key as a supported legacy header. Tokens from a Claude subscription login aren't Console API keys.

Troubleshooting table

What you see Likely cause Fix
401 right after you rotated keys The old key is still set somewhere Search env vars, .env files and CI secrets for the old value
curl works, your app fails The app loads a different value or sends it elsewhere Print the masked key and base URL from inside the app
Works locally, fails in CI The secret is empty or misnamed in CI Check the secret's name and that the job can read it
Fails only through a proxy setup Requests reach api.anthropic.com with the proxy's key Set the base URL in every code path, not only in one config
Works with chat, fails with one tool That tool sends the credential in another header Use a Console API key for that tool, or report it to its maintainers
Key worked for days, then stopped It expired or was disabled Check its expiration and status in the Console

Using a TeamoRouter key

TeamoRouter is an API gateway with one key for Claude, GPT, Gemini and DeepSeek. Claude runs on the native Anthropic protocol (/v1/messages). TeamoRouter keys start with sk-teamo-. Sent to api.anthropic.com, a TeamoRouter key is rejected like any other key Anthropic didn't issue, so set the base URL everywhere you use it. There's no subscription; you pay per use.

  1. Create an API key in the TeamoRouter console.
  2. Top up from $5. The top-up dialog takes cards. You can also click Pay with crypto and pay with USDT, USDC or ETH from any exchange app — the checkout shows which networks your amount supports (BEP20 works from $5). Step by step: Pay for Claude with USDT.
  3. Test the key:
bash
curl https://api.teamorouter.com/v1/models \
  -H "x-api-key: sk-teamo-..."

Authorization: Bearer sk-teamo-... works too. A model list means the key is good.

  1. Point the SDK at it:
python
from anthropic import Anthropic

client = Anthropic(
    api_key="sk-teamo-...",
    base_url="https://api.teamorouter.com",
)

resp = client.messages.create(
    model="claude-fable-5-1",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.content[0].text)

Leave /v1 off the base URL. The same key works for GPT on /v1/chat/completions and /v1/responses, and for Gemini. Details are in the API integration guide. For Claude Code, see Install and connect Claude Code.

FAQ

Is "invalid x-api-key" a billing problem? No. Anthropic uses 402 billing_error for billing and payment problems, and a low prepaid balance has its own message. See Anthropic API "Your credit balance is too low".

What's the difference between 401 and 403? A 401 authentication_error means the key itself was rejected. A 403 permission_error means the key is accepted but doesn't have permission for the resource you requested.

Will the error go away if I wait? No. A disabled key needs to be re-enabled. A deleted or expired key needs to be replaced with a new one.

Should I send x-api-key or Authorization? Either works with Console API keys. Anthropic's docs present Authorization: Bearer as the main header and x-api-key as a legacy header that is still supported.

Next steps

Ready to connect?Log in · top up · create an API key — three steps to start.