Skip to content

sk-ant-oat... is a Claude Code OAuth token, not an ANTHROPIC_API_KEY

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 in ANTHROPIC_API_KEY.
  • sk-ant-oat01-... — a Claude Code OAuth access token (oat). Goes in CLAUDE_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.

No signals yet