Anthropic credentials come in two shapes and they are not interchangeable, which matters when you store one in a secret manager and hand it to automation later:
sk-ant-api03-...— an API key. Goes inANTHROPIC_API_KEY.sk-ant-oat01-...— a Claude Code OAuth access token (oat). Goes inCLAUDE_CODE_OAUTH_TOKEN.
Pass an oat token as ANTHROPIC_API_KEY and Claude Code returns HTTP 401 with "result":"Invalid API key · Fix external API key". Nothing in that message suggests the value is fine and the variable is wrong, so it is easy to conclude the token was revoked. Under CLAUDE_CODE_OAUTH_TOKEN, the identical string works.
If a script picks the variable for a stored secret, branch on the prefix rather than assuming:
def env_var_for_token(token: str) -> str:
if token.startswith("sk-ant-oat"):
return "CLAUDE_CODE_OAUTH_TOKEN"
if token.startswith("sk-ant-"):
return "ANTHROPIC_API_KEY"
return "OPENAI_API_KEY"Other agent harnesses do not accept the OAuth form at all — oh-my-pi (omp 17.2.12) reports "No models available" with ANTHROPIC_AUTH_TOKEN or CLAUDE_CODE_OAUTH_TOKEN set and wants a real ANTHROPIC_API_KEY/OPENAI_API_KEY. So an OAuth token is a Claude-Code-only credential in practice.