Un 401 indique qu'un identifiant a été rejeté. Il ne prouve pas que la chaîne de votre clé est incorrecte, pas plus qu'un 403 ne signale forcément un problème de droits. Les deux fournisseurs renvoient ces codes dans des situations où l'identifiant est pourtant parfaitement valide. Il y a toutefois une vérification à faire avant toute autre : si le rejet affiche une clé masquée dont les premiers et derniers caractères ne correspondent pas à ceux de votre clé, ce n'est pas l'identifiant que vous pensiez envoyer qui arrive au serveur.
Trois erreurs 401 aux apparences trompeuses
Tous les cas ci-dessous renvoient un HTTP 401 avec un type d'erreur d'authentification, mais chacun exige une correction différente. Les messages proviennent de six requêtes envoyées à ces deux endpoints avec des clés volontairement invalides ; un mauvais identifiant est refusé avant l'étape d'autorisation, il n'est donc pas nécessaire d'utiliser une vraie clé pour reproduire les résultats.
| Ce qui s'est réellement passé | Anthropic /v1/messages | OpenAI /v1/responses |
|---|---|---|
| La clé est arrivée, puis a été refusée | API key is invalid. | Incorrect API key provided: sk-proj-**********-key. avec "code": "invalid_api_key" |
| Aucun identifiant n'est arrivé | x-api-key header is required | Missing bearer or basic authentication in header |
| L'identifiant est arrivé dans le mauvais en-tête | Invalid bearer token | Missing bearer or basic authentication in header, identique à la ligne précédente |
Anthropic distingue explicitement les trois scénarios. L'endpoint OpenAI renvoie en revanche le même message lorsque vous n'avez rien envoyé et lorsque vous avez utilisé un en-tête qu'il ne lit pas. C'est pourquoi, via un relais, on peut voir une erreur d'en-tête manquant alors que la clé est bien définie dans le shell.
Les en-têtes de réponse ne lèvent pas directement cette ambiguïté, mais ils permettent de séparer un identifiant rejeté d'un identifiant qui n'est jamais arrivé jusqu'à l'autorisation. Lancez les six appels et examinez les en-têtes plutôt que le texte du message :
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
Voici les lignes discriminantes relevées le 2026-07-31, limitées aux champs qui changent :
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
Sur les deux endpoints, un identifiant rejeté incluait le champ d'autorisation propre au fournisseur et omettait l'en-tête de challenge ; un identifiant qui n'atteignait jamais l'autorisation présentait l'inverse. Les appels répétés ont montré la même séparation, mais il s'agit d'une observation datée sur deux endpoints, non d'un comportement documenté : relancez le bloc ci-dessus au lieu de supposer qu'il s'applique à votre fournisseur. Si vous tombez dans le cas de la clé rejetée, ouvrez la page des clés OpenAI ou Claude et contrôlez quatre points : des espaces au début ou à la fin de la valeur, une clé supprimée ou révoquée, une clé créée pour un autre projet ou une autre organisation que celui appelé, et une ancienne copie encore mise en cache par le client. Ne régénérez une clé qu'après avoir écarté les trois premiers points.
Quelle identité votre CLI envoie réellement
Lorsqu'une CLI de programmation signale une clé invalide que vous n'avez jamais configurée, elle n'est probablement pas en cache : elle a été supplantée. Claude Code résout six sources selon un ordre fixe, et cette priorité documentée détermine aussi l'en-tête utilisé pour chacune.
| Priorité | Source | En-tête envoyé |
|---|---|---|
| 1 | Identifiants de fournisseurs cloud (CLAUDE_CODE_USE_BEDROCK, _VERTEX, _FOUNDRY) | spécifique au fournisseur |
| 2 | ANTHROPIC_AUTH_TOKEN | Authorization: Bearer |
| 3 | ANTHROPIC_API_KEY | X-Api-Key |
| 4 | Sortie du script apiKeyHelper | tel que renvoyé |
| 5 | CLAUDE_CODE_OAUTH_TOKEN | OAuth |
| 6 | Connexion par abonnement via /login | OAuth |
Une ANTHROPIC_API_KEY approuvée passe avant votre connexion par abonnement : /login ne la remplace donc pas. Avec le flag -p, la clé est toujours utilisée lorsqu'elle est présente. La procédure recommandée par Anthropic consiste à lancer env | grep ANTHROPIC dans le shell qui démarre l'outil, puis /status, puis à supprimer la variable si vous vouliez utiliser l'abonnement. La commande env ne couvre que les lignes 2 et 3 du tableau ; /status indique quelle source a finalement été retenue, qu'il s'agisse d'un fournisseur cloud, d'un script helper ou d'une connexion.
Cette clé que vous jurez n'avoir jamais définie
Ce signalement illustre bien le problème :
"Claude est bloqué en mode de facturation API alors qu'aucune variable d'environnement API n'est définie et que j'ai correctement lié Claude Code à mon compte Pro Max"
La première question d'un mainteneur a porté sur la présence d'un apiKeyHelper configuré. C'était le cas, et son unique instruction affichait PLACEHOLDER_NOT_IMPLEMENTED_ON_MAC_YET. Un helper qui renvoie une valeur invalide, se termine avec un code non nul ou n'affiche rien envoie un identifiant de substitution ; l'API le refuse avec un 401 qui incrimine la clé, pas le script. Claude Code nomme désormais explicitement cette erreur après trois tentatives ; cette même référence précise qu'avant la v2.1.208, les échecs du helper apparaissaient comme un 401 générique après environ dix tentatives silencieuses.
Des variables d'environnement peuvent aussi être définies sans intervention directe de votre part : Anthropic cite direnv, les plugins shell dotenv et les terminaux d'IDE parmi les sources susceptibles de charger une clé obsolète depuis un fichier .env de projet. Le helper est également relancé après cinq minutes ou à la suite d'un HTTP 401 : un helper défaillant peut donc réapparaître périodiquement.
Quand l'URL de base désigne une passerelle
Si ANTHROPIC_BASE_URL pointe vers une passerelle LLM, le texte qui suit le 401 est le message de cette passerelle, pas celui d'Anthropic, et /login ne peut rien y changer. Le même principe s'applique en sens inverse aux clients compatibles OpenAI configurés vers un relais. Dans ce signalement Codex, la chaîne d'erreur se termine par url: https://openrouter.ai/api/v1/responses :
"J'ai essayé de supprimer toutes les variables d'environnement liées aux clés API, puis le fichier d'authentification, sans résultat ; je me suis reconnecté plusieurs fois et l'erreur persiste"
Supprimer les identifiants locaux ne peut pas corriger cela : c'est l'URL de base qui détermine qui évalue la requête. Utilisez l'identifiant attendu par le service qui se trouve derrière cet endpoint. Claude Code documente ANTHROPIC_AUTH_TOKEN pour les passerelles utilisant des tokens bearer ; si vous placez la même valeur dans ANTHROPIC_API_KEY, elle est envoyée dans X-Api-Key, soit la troisième ligne du tableau ci-dessus. Les autres clients suivent leur propre contrat : vérifiez quel en-tête envoie le vôtre. Claude Code on the Web fait exception : il utilise toujours les identifiants d'abonnement, et définir l'une ou l'autre variable dans le sandbox ne les remplace pas.
Les cas de 403 où la clé est parfaitement valide
Aucun de ces cas n'a été reproduit directement, car chacun requiert un état de compte ou une localisation précise ; ils s'appuient sur la documentation des fournisseurs, qui établit sans ambiguïté qu'une clé valide peut renvoyer un 403.
La référence des erreurs d'OpenAI liste 403 - Country, region, or territory not supported : c'est un contrôle géographique, sans incidence sur la clé. Deux lignes plus haut apparaît le cas miroir, 401 - IP not authorized, renvoyé lorsque l'IP émettrice ne figure pas dans la liste d'autorisation du projet ou de l'organisation. La clé est valide, mais pas l'appelant.
Pour l'erreur API Error: 403 {"error":{"type":"forbidden","message":"Request not allowed"}} après une connexion réussie, Anthropic donne trois causes, dont aucune n'est une clé mal saisie : un abonnement Pro ou Max inactif, un compte Console sans le rôle « Claude Code » ou « Developer », ou un proxy d'entreprise qui interfère avec la requête. Sur l'API de plateforme, 403 - permission_error signifie que la clé n'a pas accès à cette ressource, selon les réglages de l'organisation et de l'espace de travail. Sur les endpoints de conformité, une clé valide dotée des mauvais scopes renvoie intentionnellement un 403 plutôt qu'un 401.
FAQ
Que signifie le code d'erreur 403 sur l'API Claude ?
Il peut désigner deux choses. permission_error indique que votre clé n'est pas autorisée à accéder à la ressource concernée ; cela relève des réglages d'organisation ou d'espace de travail. Après connexion, Request not allowed renvoie plutôt à l'état de l'abonnement, à un rôle Console manquant ou à un proxy.
Faut-il parfois réessayer après un 401 ?
Pas par défaut. Un identifiant absent ou rejeté produira le même résultat à la tentative suivante, contrairement à un 429, où respecter Retry-After avant d'appliquer un backoff est la bonne solution. Le seul 401 susceptible de se résoudre seul est celui lié à apiKeyHelper, que Claude Code réessaie déjà deux fois avant de signaler l'erreur.