AIREITER

AI Image

FLUX.2 ProGPT-Image 2Wan 2.7 Image ProGPT 4o ImageSeedream 5.0 ProSeedream V5 liteSeedream V4.5More

AI Video

Kling 3.0 Motion ControlSora 2 ProKling 3.0 TurboSora 2Kling 3.0Grok Imagine 1.5Veo 3.1More

LLM

Gemini 3.6 FlashGemini 3.1 ProKimi K3Gemini 3 ProGemini 2.5 ProClaude Opus 5Claude Fable 5More
Coming soonSeedance 2.5
Super ResolutionLyric Video GeneratorGPT Image 2 1K GeneratorGPT Image 2 Product Mockup GeneratorUse GPT-5.6 Online
API DOCSPRICING
BlogUpdatesLLM API GuideClaude API GuideKimi K3 API Guide
TEMPLATES
  • AIReiter
  • Blog
  • Invalid API Key: Diagnose 401 and 403 Before You Fix

Invalid API Key: Diagnose 401 and 403 Before You Fix

Last Updated: 2026-07-31 08:36:16

A 401 says a credential was rejected. It does not say the key string is wrong, and a 403 does not have to mean missing permissions. Both providers return each code in cases where the credential is perfectly valid. One exception is worth checking before anything else: if the rejection echoes back a masked key whose first and last characters don't match the key you hold, the credential reaching the server isn't the one you meant to send.

Three 401s That Look Identical

Every case below returns HTTP 401 with an authentication error type, but the causes need opposite fixes. The messages are what these two endpoints returned to six requests I sent with deliberately invalid keys; a bad credential is refused before authorization runs, so no real key is needed to repeat them.

What actually happenedAnthropic /v1/messagesOpenAI /v1/responses
Key arrived and was rejectedAPI key is invalid.Incorrect API key provided: sk-proj-**********-key. with "code": "invalid_api_key"
No credential arrived at allx-api-key header is requiredMissing bearer or basic authentication in header
Credential arrived on the wrong headerInvalid bearer tokenMissing bearer or basic authentication in header, identical to the previous row

Anthropic names all three. The OpenAI endpoint returns one message for both "you sent nothing" and "you sent it on a header I don't read," which is why a relay user can stare at a missing-header error while the key is demonstrably set in the shell.

The response headers do not resolve that ambiguity, but they do separate a rejected credential from one that never reached authorization. Run the six calls and read the headers rather than the prose:

show(){ shift; curl -sS -D - -o /dev/stdout "$@" \
  | grep -iE "^HTTP|www-authenticate|x-openai-authorization-error|request-id|x-should-retry|message"; }
A=(-X POST https://api.anthropic.com/v1/messages -H "anthropic-version: 2023-06-01"
   -H "content-type: application/json"
   -d '{"model":"claude-sonnet-4-5","max_tokens":8,"messages":[{"role":"user","content":"hi"}]}')
O=(-X POST https://api.openai.com/v1/responses -H "content-type: application/json"
   -d '{"model":"gpt-5.6","max_output_tokens":16,"input":"hi"}')

show a1 "${A[@]}" -H "x-api-key: sk-ant-api03-not-a-real-key"          # rejected
show a2 "${A[@]}"                                                      # never arrived
show a3 "${A[@]}" -H "Authorization: Bearer sk-ant-api03-not-a-real-key"  # wrong header
show o1 "${O[@]}" -H "Authorization: Bearer sk-proj-not-a-real-key"    # rejected
show o2 "${O[@]}"                                                      # never arrived
show o3 "${O[@]}" -H "x-api-key: sk-proj-not-a-real-key"               # wrong header

These were the discriminating lines on 2026-07-31, trimmed to the fields that differ:

o1 rejected       HTTP/2 401   x-openai-authorization-error: 401   "code": "invalid_api_key"
o2 never arrived  HTTP/2 401   www-authenticate: Bearer realm="OpenAI API"
o3 wrong header   HTTP/2 401   www-authenticate: Bearer realm="OpenAI API"
a1 rejected       HTTP/2 401   "request_id": null   (no request-id, no x-should-retry)
a2 never arrived  HTTP/2 401   request-id: req_011CdZnC9j…   x-should-retry: false
a3 wrong header   HTTP/2 401   request-id: req_011CdZnCDN…   x-should-retry: false

On both endpoints a rejected credential carried the vendor's own authorization field and dropped the challenge header, while one that never reached authorization did the reverse. Repeat calls gave the same split, but this is a dated observation on two endpoints, not documented behaviour: re-run the block above rather than assuming it holds for your provider. Landing in the rejected row puts you on the OpenAI or Claude key page with four things to check: leading or trailing whitespace in the value, a key that was deleted or revoked, a key issued to a different project or organization than the one you are calling, and a stale copy still cached by the client. Regenerate only after the first three come back clean.

Which Credential Your CLI Actually Sent

When a coding CLI reports an invalid key you did not configure, the credential was not cached. It was outranked. Claude Code resolves six sources in a fixed order, and that documented precedence also fixes which header each one travels on.

Claude Code documentation listing the authentication precedence order and the header each credential uses
PrioritySourceHeader sent
1Cloud provider credentials (CLAUDE_CODE_USE_BEDROCK, _VERTEX, _FOUNDRY)provider-specific
2ANTHROPIC_AUTH_TOKENAuthorization: Bearer
3ANTHROPIC_API_KEYX-Api-Key
4apiKeyHelper script outputas returned
5CLAUDE_CODE_OAUTH_TOKENOAuth
6Subscription login from /loginOAuth

An approved ANTHROPIC_API_KEY sits above your subscription login, so /login does not displace it; with the -p flag the key is always used when present. Anthropic's own fix list is env | grep ANTHROPIC in the launching shell, then /status, then unset the variable if you meant to use the subscription. The env check only covers rows 2 and 3 of the table; /status is what reports a cloud provider, a helper script or a login as the resolved source.

Claude Code error reference entry for Invalid API key with the five official diagnostic steps

The credential you swear you never set

One report shows the shape it takes:

"Claude is stuck in API billing mode even though there is no API env variable set and I have linked claude code to my pro max account successfully"

A maintainer's first question was whether an apiKeyHelper was configured. It was, and its entire body printed PLACEHOLDER_NOT_IMPLEMENTED_ON_MAC_YET. A helper that returns garbage, exits non-zero, or prints nothing sends a placeholder credential, and the API rejects it with a 401 that describes the key rather than the script. Claude Code now reports that failure by name within three attempts; the same entry records that before v2.1.208 helper failures surfaced as a generic 401 after about ten silent retries.

Environment variables also get set without your involvement: Anthropic lists direnv, dotenv shell plugins and IDE terminals as sources that load a stale key from a project .env file. The helper is also re-invoked after five minutes or on an HTTP 401, so a broken one reappears on a timer.

When the base URL points at a gateway

If ANTHROPIC_BASE_URL points at an LLM gateway, the text after 401 is the gateway's message, not Anthropic's, and /login cannot change it. The same applies in reverse to OpenAI-compatible clients pointed at a relay. From a Codex report whose error string ends in url: https://openrouter.ai/api/v1/responses:

"I tried clearing all the env variables related to api keys , tried deleting the auth file too didn't work relogged in many times error persists"

Clearing local credentials cannot fix that: the base URL decides who judges the request. Match the credential to what sits at that endpoint. Claude Code documents ANTHROPIC_AUTH_TOKEN for gateways that authenticate with bearer tokens; put the same value in ANTHROPIC_API_KEY and it goes out as X-Api-Key instead, which is row three of the table above. Other clients follow their own contract; check which header yours sends. Claude Code on the Web is the exception: it always uses subscription credentials, and setting either variable in the sandbox does not override them.

403 Cases Where the Key Is Fine

None of this is reproduced first-hand, since each case needs a specific account state or geography; it follows the provider documentation, which is unambiguous that a valid key can produce a 403.

OpenAI's error reference lists 403 - Country, region, or territory not supported — a geography check, with the key untouched. Two rows above it sits the mirror image, 401 - IP not authorized, raised when the request IP is outside the project or organization allowlist. A valid key, an invalid caller.

OpenAI error codes reference showing 401 IP not authorized above 403 Country region or territory not supported

For API Error: 403 {"error":{"type":"forbidden","message":"Request not allowed"}} after a successful login, Anthropic gives three causes and none of them is a mistyped key: an inactive Pro or Max subscription, a Console account missing the "Claude Code" or "Developer" role, or a corporate proxy interfering with the request. On the platform API, 403 - permission_error means the key lacks permission for that resource, checked against organization and workspace settings, and on the compliance endpoints a valid key with the wrong scopes returns 403 rather than 401 by design.

FAQ

What does error code 403 mean on the Claude API?

Two different things. permission_error means your key has no permission for that resource, which is an organization or workspace setting. Request not allowed after login points instead at subscription status, a missing Console role, or a proxy.

Is a 401 ever worth retrying?

Not on its own. A rejected or absent credential returns the same result next attempt, unlike a 429, where respecting Retry-After before backing off is the fix. The one 401 that resolves itself is the apiKeyHelper case, which Claude Code already retries twice more before reporting.

Related reading

  • Fix OpenRouter 429: Provider Error or Rate Limit?
  • what upstream connect error or disconnect/reset before headers means on the OpenAI API

>_AIReiter Model Directory

Fast API access to models related to this guide

Claude Sonnet 5

Chat

A balanced Claude model for advanced reasoning, coding, and everyday work.

AnthropicGet API Key >

GPT-5.6 Sol

Chat

A premium GPT-5.6 text model for demanding coding, reasoning, and long-form agent work.

OpenAIGet API Key >

Claude Fable 5

Chat

A premium Claude model for deep reasoning and complex long-form work.

AnthropicGet API Key >

Claude Opus 4.8

Chat

A high-capability Claude model for demanding reasoning and professional work.

AnthropicGet API Key >

Claude Opus 5

Chat

A premium Claude model for complex reasoning, coding, and long-context professional work.

anthropicGet API Key >

Recent Posts

GPT-5.6 Price Cut: What Luna and Terra Really Cost Now

2026-07-31

Fix OpenRouter 429: Provider Error or Rate Limit?

2026-07-31

DeepSeek V4 Flash vs GLM-5.2: 0731 Update Tested

2026-07-31

B2B Ad Intelligence Only Comes From HTML: Writing a Parser That Survives Redesigns

2026-07-31
AIREITER

Questions? Contact us at
[email protected]

LLM

Gemini 3.6 FlashGemini 3.1 ProKimi K3Gemini 3 ProGemini 2.5 Pro

AI Video

Kling 3.0 Motion ControlSora 2 ProKling 3.0 TurboSora 2Kling 3.0

AI Image

FLUX.2 ProGPT-Image 2Wan 2.7 Image ProGPT 4o ImageSeedream 5.0 Pro

Blog

View All →

Company

Privacy PolicyTerms of ServiceRefund Policy

© 2026 AIReiter. All rights reserved.