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
/statusnames an unexpected API key — unsetANTHROPIC_API_KEYin the shell you launchclaudefrom, 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_DIRconsistent. Verify by quitting, relaunching, and running/statusbefore you send any prompt. - Only fails behind a gateway — confirm what
ANTHROPIC_BASE_URLis 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 doctorfrom 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.
Related errors
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.