Um erro 401 informa que uma credencial foi rejeitada, não que a string da chave esteja necessariamente errada. Da mesma forma, um 403 não implica obrigatoriamente falta de permissões. Os dois provedores retornam esses códigos em situações nas quais a credencial é perfeitamente válida. Antes de investigar qualquer outra coisa, vale conferir uma pista: se a rejeição devolve uma chave mascarada cujos primeiros e últimos caracteres não batem com a chave que você tem, o servidor está recebendo uma credencial diferente da que você pretendia enviar.
Três erros 401 com a mesma cara
Todos os casos abaixo retornam HTTP 401 com um erro de autenticação, mas cada um exige uma correção diferente. As mensagens foram retornadas por estes dois endpoints em seis requisições que enviei usando chaves deliberadamente inválidas. Como uma credencial ruim é recusada antes da etapa de autorização, não é preciso ter uma chave real para reproduzir os testes.
| O que aconteceu de fato | Anthropic /v1/messages | OpenAI /v1/responses |
|---|---|---|
| A chave chegou e foi rejeitada | API key is invalid. | Incorrect API key provided: sk-proj-**********-key. com "code": "invalid_api_key" |
| Nenhuma credencial chegou | x-api-key header is required | Missing bearer or basic authentication in header |
| A credencial chegou no cabeçalho errado | Invalid bearer token | Missing bearer or basic authentication in header, idêntico à linha anterior |
A Anthropic distingue explicitamente os três cenários. Já o endpoint da OpenAI usa a mesma mensagem tanto para “você não enviou nada” quanto para “você enviou em um cabeçalho que eu não leio”. É por isso que quem usa um relay pode encarar um erro de cabeçalho ausente mesmo com a chave comprovadamente definida no shell.
Os cabeçalhos da resposta não eliminam essa ambiguidade, mas ajudam a separar uma credencial rejeitada de uma que sequer chegou à autorização. Execute as seis chamadas e examine os cabeçalhos, não só o texto da mensagem:
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
Em 2026-07-31, estas foram as linhas que permitiram diferenciar os casos, reduzidas aos campos que mudam:
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
Nos dois endpoints, uma credencial rejeitada trazia o campo de autorização do próprio fornecedor e não incluía o cabeçalho de desafio; quando a credencial não chegava à autorização, ocorria o inverso. Chamadas repetidas mantiveram essa divisão, mas esta é uma observação datada em dois endpoints, não um comportamento documentado. Execute o bloco acima novamente em vez de presumir que o resultado vale para seu provedor. Se você cair na linha de credencial rejeitada, vá à página de chaves da OpenAI ou do Claude e confira quatro pontos: espaços no início ou fim do valor, uma chave excluída ou revogada, uma chave emitida para outro projeto ou organização que não aquele que você está chamando e uma cópia antiga ainda em cache no cliente. Gere uma nova chave somente depois de descartar os três primeiros itens.
Qual credencial sua CLI realmente enviou
Se uma CLI de programação acusa uma chave inválida que você não configurou, ela não veio de cache: foi priorizada acima de outra credencial. O Claude Code resolve seis fontes em uma ordem fixa, e essa precedência documentada também determina em qual cabeçalho cada uma é enviada.
| Prioridade | Fonte | Cabeçalho enviado |
|---|---|---|
| 1 | Credenciais de provedores de nuvem (CLAUDE_CODE_USE_BEDROCK, _VERTEX, _FOUNDRY) | específico do provedor |
| 2 | ANTHROPIC_AUTH_TOKEN | Authorization: Bearer |
| 3 | ANTHROPIC_API_KEY | X-Api-Key |
| 4 | Saída do script apiKeyHelper | como retornado |
| 5 | CLAUDE_CODE_OAUTH_TOKEN | OAuth |
| 6 | Login de assinatura via /login | OAuth |
Uma ANTHROPIC_API_KEY aprovada fica acima do login da sua assinatura; portanto, /login não a substitui. Com a flag -p, a chave sempre será usada quando estiver presente. A lista de correções da própria Anthropic é executar env | grep ANTHROPIC no shell que inicia o programa, depois /status e, se a intenção for usar a assinatura, remover a variável. A verificação com env cobre somente as linhas 2 e 3 da tabela; é o /status que informa se a fonte resolvida foi um provedor de nuvem, um script auxiliar ou um login.
A credencial que você jura nunca ter definido
Este relato ilustra bem o problema:
"O Claude ficou preso no modo de cobrança por API, mesmo sem nenhuma variável de ambiente de API definida e apesar de eu ter vinculado o Claude Code à minha conta Pro Max com sucesso"
A primeira pergunta de um mantenedor foi se havia um apiKeyHelper configurado. Havia, e todo o conteúdo dele imprimia PLACEHOLDER_NOT_IMPLEMENTED_ON_MAC_YET. Um helper que devolve lixo, encerra com código diferente de zero ou não imprime nada envia uma credencial de preenchimento. A API então a rejeita com um 401 que descreve a chave, não o script. Hoje, o Claude Code identifica essa falha pelo nome em até três tentativas; a mesma documentação registra que, antes da v2.1.208, falhas do helper apareciam como um 401 genérico após cerca de dez novas tentativas silenciosas.
Variáveis de ambiente também podem surgir sem ação direta sua: a Anthropic lista direnv, plugins de shell para dotenv e terminais de IDE como fontes que carregam uma chave antiga de um arquivo .env do projeto. O helper também é executado novamente após cinco minutos ou diante de um HTTP 401; se estiver quebrado, ele volta a aparecer periodicamente.
Quando a URL base aponta para um gateway
Se ANTHROPIC_BASE_URL aponta para um gateway de LLM, o texto depois do 401 vem do gateway, não da Anthropic, e /login não consegue alterá-lo. O mesmo vale no sentido inverso para clientes compatíveis com OpenAI que apontam para um relay. Em um relato sobre o Codex cujo texto do erro termina em url: https://openrouter.ai/api/v1/responses:
"Tentei limpar todas as variáveis de ambiente relacionadas a chaves de API, tentei apagar também o arquivo de autenticação, não funcionou; fiz login de novo várias vezes e o erro continua"
Limpar as credenciais locais não resolve isso: a URL base define quem avalia a requisição. Use a credencial compatível com o serviço que está naquele endpoint. O Claude Code documenta ANTHROPIC_AUTH_TOKEN para gateways que autenticam com tokens bearer; se você colocar o mesmo valor em ANTHROPIC_API_KEY, ele será enviado como X-Api-Key, exatamente como na terceira linha da tabela. Outros clientes seguem seus próprios contratos, então confira qual cabeçalho o seu envia. O Claude Code on the Web é a exceção: ele sempre usa credenciais da assinatura, e definir qualquer uma das variáveis no sandbox não as substitui.
Quando um 403 não tem nada de errado com a chave
Nenhum dos casos desta seção foi reproduzido diretamente, pois cada um exige uma condição específica de conta ou localização geográfica. Eles seguem a documentação dos provedores, que deixa claro que uma chave válida também pode resultar em 403.
A referência de erros da OpenAI lista 403 - Country, region, or territory not supported: é uma verificação geográfica, sem qualquer problema na chave. Duas linhas acima está o caso espelhado, 401 - IP not authorized, emitido quando o IP da requisição está fora da lista de permissão do projeto ou da organização. A chave é válida; quem faz a chamada não é autorizado.
Para o erro API Error: 403 {"error":{"type":"forbidden","message":"Request not allowed"}} após um login bem-sucedido, a Anthropic aponta três causas — e nenhuma é uma chave digitada incorretamente: assinatura Pro ou Max inativa, conta Console sem o papel “Claude Code” ou “Developer”, ou um proxy corporativo interferindo na requisição. Na API da plataforma, 403 - permission_error indica que a chave não tem permissão para aquele recurso, conforme as configurações da organização e do workspace. Já nos endpoints de compliance, uma chave válida com escopos incorretos retorna 403, e não 401, por definição.
Perguntas frequentes
O que significa o código de erro 403 na API do Claude?
Ele pode indicar duas coisas diferentes. permission_error significa que sua chave não tem permissão para aquele recurso, uma definição da organização ou do workspace. Já Request not allowed após o login aponta para o status da assinatura, um papel ausente no Console ou um proxy.
Vale a pena tentar novamente após um 401?
Não por si só. Uma credencial rejeitada ou ausente terá o mesmo resultado na tentativa seguinte, ao contrário de um 429, em que a correção é respeitar o Retry-After antes de aplicar backoff. O único 401 que pode se resolver sozinho é o caso do apiKeyHelper, que o Claude Code já tenta mais duas vezes antes de reportar.