Skip to content

Invalid API key · Please run /login

A credential was found, sent, and rejected. That single fact rules out most of what people suspect when they see this line: it is not a network problem — the request reached Anthropic and came back — it is not a quota or billing problem, which produce their own distinct wording, and it is not a missing credential, which fails before any request leaves your machine. Something authenticated as you, and the server said no.

The trap is in the second half of the message. Please run /login is advice, not a diagnosis, and there is a common configuration in which running /login succeeds, reports success, and changes nothing at all.

The credential that failed may not be the one you logged in with

Claude Code can authenticate several ways, and they are not ranked the way most people assume. A subscription sign-in through /login is one source. An ANTHROPIC_API_KEY in the environment is another. A gateway named by ANTHROPIC_BASE_URL is a third path with its own credential expectations.

The official docs call out the resulting misdiagnosis directly, in the context of a different error: a subscriber who sees a Console billing message has, in practice, a stray ANTHROPIC_API_KEY in the environment routing requests through a Console key instead of through the subscription they are paying for. The same mechanism produces this error. If an API key is present in your environment and that key is malformed, revoked, expired, or simply belongs to a different organization, you get Invalid API key no matter how cleanly your /login completed — because /login writes the subscription credential and never touches the environment variable that is winning.

That is the loop people get stuck in: error, /login, “Login successful”, same error, /login again. Each cycle re-proves that the subscription works and never reaches the credential that is actually being sent.

/status ends this in one step. It shows the active settings sources and which credential is active. Run it before you change anything. If it names an API key and you expected a subscription, you have found the problem and the rest of this page’s causes do not apply to you.

Checking for the stray key without exposing it

Check whether the variable is set, not what it contains. env | grep -c ANTHROPIC_API_KEY answers the question with a count and prints nothing sensitive. A shell test works too: [ -n "$ANTHROPIC_API_KEY" ] && echo set.

Do not echo the value into a terminal you might screenshot, and do not paste a key into a chat window, a web form, a pastebin, or any third-party “key checker” site — a key that has been pasted anywhere outside your own machine should be treated as compromised and rotated in the vendor console. When you need to identify a key, use the last few characters, which are enough to tell two keys apart and useless on their own.

The variable can be set somewhere you are not looking: a shell profile, a .env file your editor loads, a container image, a CI secret injected into your local runner, or a wrapper script. Find it with env first; hunt for the source second.

Telling the four causes apart

/status names a credential you did not intend. An environment variable or a gateway is shadowing your sign-in. This is the most common case by a wide margin and the only one where /login is guaranteed not to help.

/status names the credential you expected, and it fails on every request including the first of a fresh session. The credential itself is being refused. Per the API docs, a 401 with error.type: authentication_error means the key is malformed, revoked, or expired — three different repairs. Malformed usually means a truncated copy or stray whitespace; revoked and expired both mean the credential needs replacing at the source.

Login succeeds, works for a while, and the error returns after you restart. The credential is not persisting. Claude Code keeps global app state and OAuth in ~/.claude.json; on Windows, ~ means %USERPROFILE%. Confirm that path is writable by the user actually running claude, and that CLAUDE_CONFIG_DIR — which relocates Claude Code’s configuration — is either unset everywhere or set identically in the shell where you log in and the shell where you fail. Logging in under one value and running under another means two different stores.

It fails only through one gateway or on one machine. You are pointed at an ANTHROPIC_BASE_URL that does not accept the credential you are sending. The corroborating observation is that other errors from this setup name the gateway host rather than Anthropic’s. A 401 that says OAuth is not supported is the sharper version of this same problem, and worth reading if your gateway is a relay or a cloud deployment.

Is retrying useful?

No. An identical request with the same credential fails identically, every time.

Authentication is a deterministic verdict, not a capacity decision. There is no backoff window that turns a rejected credential into an accepted one, and no retry-after header to honor, because the server is not asking you to wait. If you hit Enter again and get the same line, that is not bad luck — it is confirmation that you are looking at a fixed configuration and the rest of this page applies.

The one thing worth re-running is /status, after each change, because it is the only step here that reports state rather than guessing at it.

Fix by scenario

  • /status names an unexpected API key — unset ANTHROPIC_API_KEY in the shell you launch claude from, then relaunch and re-check /status. Fix the file that exports it, or you will be doing this again tomorrow in a new terminal.
  • You intended to use the API key — replace it. A malformed, revoked, or expired key cannot be repaired in place; generate a new one in the vendor console and set it in one place only.
  • Credential does not survive a restart — check write permissions on the configuration path and make CLAUDE_CONFIG_DIR consistent. Verify by quitting, relaunching, and running /status before you send any prompt.
  • Only fails behind a gateway — confirm what ANTHROPIC_BASE_URL is set to and what that endpoint expects. A relay that wants an API key will not accept a subscription sign-in regardless of how many times you repeat it.
  • Nothing above matches — run claude doctor from the shell for a read-only installation and settings check. It runs without starting a session, which matters when the failure prevents one.

How to confirm it’s fixed

Two observations, in order. First, /status names the credential you intend to use — not “no error”, but the right source by name. Second, a real request succeeds in a freshly launched session from a new terminal, because the old terminal still holds the environment you just changed and will keep lying to you. If the first request after a clean relaunch works, the resolution order is correct.

If you changed a shell profile, open a terminal you have never used since the edit. A fix that only works in the window where you typed unset is not a fix.

Same message family, different failure. Missing API key · Run /login means no credential resolved at all — the request was never sent, so none of the “which key won” reasoning here applies. If your login itself is failing rather than your requests, the exchange is the problem, not the credential: a login that times out at a fixed deadline and a login that gets a 500 back are network-path and vendor-side stories respectively. When you are not sure which of these you have, the AI coding error triage tool sorts them by whether a request was sent, answered, or rejected.