Un errore 401 comunica che una credenziale è stata rifiutata, non che la stringa della chiave sia necessariamente sbagliata. Allo stesso modo, un 403 non equivale sempre a permessi mancanti. Entrambi i provider usano questi codici anche quando la credenziale è perfettamente valida. C'è però un controllo da fare subito: se la risposta riporta una chiave mascherata e i suoi primi o ultimi caratteri non coincidono con quelli della chiave in tuo possesso, al server sta arrivando una credenziale diversa da quella che intendevi inviare.
Tre scenari 401 con lo stesso aspetto
Tutti i casi seguenti restituiscono HTTP 401 con un errore di autenticazione, ma richiedono interventi opposti. I messaggi sono quelli ottenuti da questi due endpoint in sei richieste con chiavi volutamente non valide; una credenziale errata viene rifiutata prima della fase di autorizzazione, quindi non serve una chiave reale per riprodurli.
| Cosa è successo davvero | Anthropic /v1/messages | OpenAI /v1/responses |
|---|---|---|
| La chiave è arrivata ed è stata rifiutata | API key is invalid. | Incorrect API key provided: sk-proj-**********-key. con "code": "invalid_api_key" |
| Non è arrivata alcuna credenziale | x-api-key header is required | Missing bearer or basic authentication in header |
| La credenziale è arrivata nell'header sbagliato | Invalid bearer token | Missing bearer or basic authentication in header, identico alla riga precedente |
Anthropic distingue esplicitamente tutti e tre i casi. L'endpoint OpenAI, invece, usa lo stesso messaggio sia quando non hai inviato nulla sia quando hai usato un header che non legge. Ecco perché chi usa un relay può trovarsi davanti a un errore di header mancante pur avendo la chiave chiaramente impostata nella shell.
Gli header della risposta non eliminano quell'ambiguità, ma permettono di separare una credenziale rifiutata da una che non è mai arrivata all'autorizzazione. Esegui le sei chiamate e guarda gli header, non solo il testo dell'errore:
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
Queste erano le righe utili per distinguere i casi il 2026-07-31, ridotte ai soli campi diversi:
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
Su entrambi gli endpoint, una credenziale rifiutata riportava il campo di autorizzazione proprietario del provider e non l'header di challenge; una credenziale che non raggiungeva l'autorizzazione mostrava invece il comportamento opposto. Le chiamate ripetute hanno dato la stessa separazione, ma si tratta di un'osservazione datata su due endpoint, non di un comportamento documentato: ripeti il blocco qui sopra invece di presumere che valga per il tuo provider. Se finisci nel caso della credenziale rifiutata, vai alla pagina delle chiavi OpenAI o Claude e controlla quattro aspetti: spazi iniziali o finali nel valore, una chiave eliminata o revocata, una chiave emessa per un progetto o un'organizzazione diversi da quelli chiamati e una copia obsoleta ancora in cache nel client. Rigenera la chiave solo dopo aver escluso i primi tre.
Quale credenziale sta davvero inviando la tua CLI
Se una CLI per il coding segnala una chiave non valida che non ricordi di aver configurato, non è detto che sia in cache: potrebbe essere stata superata da una fonte con priorità maggiore. Claude Code risolve sei sorgenti in un ordine fisso e questa precedenza documentata determina anche l'header usato per ciascuna.
| Priorità | Origine | Header inviato |
|---|---|---|
| 1 | Credenziali del cloud provider (CLAUDE_CODE_USE_BEDROCK, _VERTEX, _FOUNDRY) | specifico del provider |
| 2 | ANTHROPIC_AUTH_TOKEN | Authorization: Bearer |
| 3 | ANTHROPIC_API_KEY | X-Api-Key |
| 4 | Output dello script apiKeyHelper | come restituito |
| 5 | CLAUDE_CODE_OAUTH_TOKEN | OAuth |
| 6 | Login dell'abbonamento tramite /login | OAuth |
Una ANTHROPIC_API_KEY valida ha priorità sul login dell'abbonamento, quindi /login non la sostituisce; usando il flag -p, se la chiave è presente viene sempre utilizzata. La procedura indicata da Anthropic è eseguire env | grep ANTHROPIC nella shell che avvia il programma, poi /status, quindi rimuovere la variabile se volevi usare l'abbonamento. Il controllo con env copre soltanto le righe 2 e 3 della tabella; /status rivela invece se la fonte risolta è un cloud provider, uno script helper o un login.
La credenziale che sei certo di non aver impostato
Questo report mostra bene il problema:
"Claude resta bloccato in modalità fatturazione API anche se non è impostata alcuna variabile d'ambiente API e ho collegato Claude Code correttamente al mio account Pro Max"
La prima domanda di un maintainer è stata se fosse configurato un apiKeyHelper. Lo era, e il suo intero contenuto stampava PLACEHOLDER_NOT_IMPLEMENTED_ON_MAC_YET. Un helper che restituisce dati inutilizzabili, termina con un codice diverso da zero o non stampa nulla invia una credenziale segnaposto; l'API la rifiuta con un 401 che descrive la chiave anziché lo script. Claude Code ora segnala esplicitamente questo errore entro tre tentativi; la stessa voce documenta che prima della v2.1.208 gli errori dell'helper comparivano come un generico 401 dopo circa dieci tentativi silenziosi.
Anche le variabili d'ambiente possono comparire senza un tuo intervento: Anthropic indica direnv, plugin shell dotenv e terminali degli IDE tra le fonti che caricano una chiave obsoleta da un file .env del progetto. Inoltre, l'helper viene richiamato dopo cinque minuti o in seguito a un HTTP 401, quindi uno script difettoso può ripresentarsi a intervalli regolari.
Quando il base URL punta a un gateway
Se ANTHROPIC_BASE_URL punta a un gateway LLM, il testo che segue 401 è il messaggio del gateway, non di Anthropic, e /login non può modificarlo. Lo stesso vale al contrario per i client compatibili con OpenAI che puntano a un relay. Da un report di Codex, la cui stringa di errore termina con url: https://openrouter.ai/api/v1/responses:
"Ho provato a cancellare tutte le variabili d'ambiente relative alle API key, ho provato anche a eliminare il file di autenticazione, non è cambiato nulla; ho effettuato di nuovo il login molte volte ma l'errore persiste"
Eliminare le credenziali locali non può risolvere il problema: è il base URL a decidere chi valuta la richiesta. Usa una credenziale compatibile con ciò che si trova dietro quell'endpoint. Claude Code documenta ANTHROPIC_AUTH_TOKEN per i gateway che usano bearer token; se inserisci lo stesso valore in ANTHROPIC_API_KEY, verrà invece inviato come X-Api-Key, cioè la terza riga della tabella. Gli altri client seguono il proprio contratto: verifica quale header invia il tuo. Claude Code on the Web è l'eccezione: usa sempre le credenziali dell'abbonamento e impostare una delle due variabili nel sandbox non le sovrascrive.
Quando un 403 non è un problema della chiave
Nessuno di questi casi è stato riprodotto direttamente, perché ognuno richiede uno stato specifico dell'account o una determinata area geografica; la documentazione dei provider è però inequivocabile: una chiave valida può generare un 403.
La guida agli errori di OpenAI include 403 - Country, region, or territory not supported: è un controllo geografico, senza alcun problema nella chiave. Due righe più sopra compare il caso speculare, 401 - IP not authorized, restituito quando l'IP della richiesta è fuori dalla allowlist del progetto o dell'organizzazione. Chiave valida, chiamante non valido.
Per API Error: 403 {"error":{"type":"forbidden","message":"Request not allowed"}} dopo un login riuscito, Anthropic indica tre cause, nessuna delle quali è una chiave digitata male: un abbonamento Pro o Max non attivo, un account Console senza il ruolo "Claude Code" o "Developer", oppure un proxy aziendale che interferisce con la richiesta. Sulla platform API, 403 - permission_error significa che la chiave non ha l'autorizzazione per quella risorsa, in base alle impostazioni dell'organizzazione e del workspace; negli endpoint di compliance, invece, una chiave valida con scope errati restituisce intenzionalmente 403 anziché 401.
FAQ
Cosa significa il codice errore 403 nella Claude API?
Può indicare due situazioni diverse. permission_error significa che la chiave non dispone dell'autorizzazione per quella risorsa, un'impostazione legata all'organizzazione o al workspace. Request not allowed dopo il login rimanda invece allo stato dell'abbonamento, a un ruolo Console mancante o a un proxy.
Vale mai la pena ritentare dopo un 401?
Non di per sé. Una credenziale rifiutata o assente darà lo stesso risultato al tentativo successivo, a differenza di un 429, per il quale rispettare Retry-After prima di applicare un backoff è la soluzione. L'unico 401 che può risolversi da solo è il caso di apiKeyHelper, che Claude Code ritenta già altre due volte prima di segnalarlo.