401が返ってきたからといって、APIキーの文字列が間違っているとは限りません。403も、必ずしも権限不足を意味するわけではありません。どちらのプロバイダーでも、キーそのものは有効なのにこれらのステータスコードを返すケースがあります。まず確認したい例外がひとつあります。エラーにマスクされたキーが表示され、その先頭や末尾の文字が手元のキーと一致しないなら、サーバーへ届いている認証情報は意図したものではありません。
見た目は同じでも原因が異なる3種類の401
以下はいずれも認証エラー型のHTTP 401ですが、必要な対処は正反対です。意図的に無効なキーを使って2つのエンドポイントへ6回送信し、返ってきたメッセージを確認しました。認証情報が不正なら認可処理より前で拒否されるため、再現に実在するキーは必要ありません。
| 実際に起きていること | Anthropic /v1/messages | OpenAI /v1/responses |
|---|---|---|
| キーは届いたが拒否された | API key is invalid. | Incorrect API key provided: sk-proj-**********-key. と "code": "invalid_api_key" |
| 認証情報がまったく届いていない | x-api-key header is required | Missing bearer or basic authentication in header |
| 認証情報は届いたがヘッダーが違う | Invalid bearer token | Missing bearer or basic authentication in header。前行と同一 |
Anthropicはこの3パターンをそれぞれ明示します。一方、OpenAIのエンドポイントでは「何も送っていない」と「読み取らないヘッダーで送った」が同じメッセージになります。そのためリレー経由では、シェルにキーが設定されているのに、ヘッダーがないというエラーを見続けることがあります。
レスポンスヘッダーだけでこの曖昧さを解消することはできません。ただし、認証情報が拒否されたのか、認可処理まで到達していないのかは区別できます。文面ではなくヘッダーを見るため、以下の6リクエストを実行してください。
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" # 拒否
show a2 "${A[@]}" # 未到達
show a3 "${A[@]}" -H "Authorization: Bearer sk-ant-api03-not-a-real-key" # ヘッダー違い
show o1 "${O[@]}" -H "Authorization: Bearer sk-proj-not-a-real-key" # 拒否
show o2 "${O[@]}" # 未到達
show o3 "${O[@]}" -H "x-api-key: sk-proj-not-a-real-key" # ヘッダー違い
2026-07-31時点で差分として確認できた行は次のとおりです。異なるフィールドだけを抜き出しています。
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
両エンドポイントでは、認証情報が拒否された場合にプロバイダー独自の認証フィールドが付き、チャレンジヘッダーは消えました。認可処理に届かなかった場合は逆です。繰り返しても同じ傾向でしたが、これは2つのエンドポイントを対象とした特定日時の観測であり、仕様として文書化された挙動ではありません。プロバイダーでも同じだと決めつけず、上のブロックを再実行してください。拒否された行に該当した場合は、OpenAIまたはClaudeのキーページで4点を確認します。値の先頭・末尾に空白がないか、キーが削除または失効していないか、呼び出している先とは別のプロジェクトまたは組織で発行されたキーではないか、クライアントに古いコピーがキャッシュされていないかです。最初の3点に問題がないことを確認してから再生成してください。
CLIが実際に送信した認証情報を確認する
コーディングCLIが、自分で設定した覚えのない無効なキーを報告する場合、その認証情報はキャッシュではありません。より優先順位の高い設定に上書きされています。Claude Codeは6つの認証情報ソースを固定順で解決し、その文書化された優先順位によって、どのヘッダーで送られるかも決まります。
| 優先順位 | ソース | 送信されるヘッダー |
|---|---|---|
| 1 | クラウドプロバイダー認証情報(CLAUDE_CODE_USE_BEDROCK、_VERTEX、_FOUNDRY) | プロバイダー固有 |
| 2 | ANTHROPIC_AUTH_TOKEN | Authorization: Bearer |
| 3 | ANTHROPIC_API_KEY | X-Api-Key |
| 4 | apiKeyHelperスクリプトの出力 | 返された形式のまま |
| 5 | CLAUDE_CODE_OAUTH_TOKEN | OAuth |
| 6 | /loginによるサブスクリプションログイン | OAuth |
有効なANTHROPIC_API_KEYはサブスクリプションログインより上位にあるため、/loginを実行しても置き換わりません。-pフラグ使用時は、キーがあれば常にそちらが使われます。Anthropicが案内する手順は、起動元シェルでenv | grep ANTHROPICを実行し、続いて/statusを確認し、サブスクリプションを使いたいなら変数をunsetするというものです。envで確認できるのは表の2行目と3行目だけです。クラウドプロバイダー、ヘルパースクリプト、ログインのどれが解決されたソースかを報告するのは/statusです。
設定した覚えのない認証情報が使われる理由
実際には、次の報告のような形で現れます。
"API用の環境変数は設定されておらず、claude codeもpro maxアカウントへ正常にリンクしたのに、ClaudeがAPI課金モードのまま動かなくなった"
メンテナーが最初に確認したのは、apiKeyHelperが設定されているかどうかでした。実際に設定されており、その中身はPLACEHOLDER_NOT_IMPLEMENTED_ON_MAC_YETを出力するだけでした。不正な値を返す、ゼロ以外で終了する、何も出力しないヘルパーは、プレースホルダーの認証情報を送信します。APIはスクリプトではなくキーについて説明する401でこれを拒否します。Claude Codeは現在、3回目までの試行でこの失敗を名前付きで報告します。同じ項目には、v2.1.208以前ではヘルパーの失敗が約10回の無言リトライ後に一般的な401として表示されていたことも記載されています。
環境変数は自分で設定しなくても入り込むことがあります。Anthropicは、プロジェクトの.envから古いキーを読み込む要因として、direnv、dotenv対応シェルプラグイン、IDEのターミナルを挙げています。さらにヘルパーは、5分後またはHTTP 401発生時に再実行されます。壊れたヘルパーなら、時間が経つと再び問題を起こします。
ベースURLがゲートウェイを指している場合
ANTHROPIC_BASE_URLがLLMゲートウェイを指しているなら、401の後に表示される文面はAnthropicではなくゲートウェイからのメッセージです。この場合、/loginでは変わりません。OpenAI互換クライアントをリレーへ向けている場合も同様です。エラー文字列の末尾がurl: https://openrouter.ai/api/v1/responsesとなっているCodexの報告では、次のように書かれています。
"APIキー関連の環境変数をすべて消し、authファイルも削除したが直らなかった。何度も再ログインしたがエラーが続く"
ローカルの認証情報を消しても解決しません。リクエストを判定する相手はベースURLで決まるからです。そのエンドポイントにあるサービスに合わせて認証情報を設定してください。Claude Codeは、Bearerトークンで認証するゲートウェイ向けにANTHROPIC_AUTH_TOKENを案内しています。同じ値をANTHROPIC_API_KEYへ入れると、表の3行目のとおりX-Api-Keyとして送信されます。他のクライアントにはそれぞれの契約があります。どのヘッダーを送っているか確認してください。例外はClaude Code on the Webで、常にサブスクリプション認証情報を使用します。サンドボックスでどちらの変数を設定しても上書きできません。
キーが有効でも発生する403
ここからのケースは、いずれも特定のアカウント状態や地域が必要なため、直接再現したものではありません。ただしプロバイダーのドキュメントに基づくもので、有効なキーでも403になり得ることは明確です。
OpenAIのエラーリファレンスには、403 - Country, region, or territory not supportedが掲載されています。これは地域チェックであり、キーは関係ありません。その2行上には逆の例である401 - IP not authorizedがあります。リクエスト元IPがプロジェクトまたは組織の許可リスト外にあると発生します。キーは有効でも、呼び出し元が無効という状態です。
ログイン成功後にAPI Error: 403 {"error":{"type":"forbidden","message":"Request not allowed"}}が出る場合、Anthropicは3つの原因を示しています。キーの打ち間違いは含まれません。ProまたはMaxサブスクリプションが非アクティブ、Consoleアカウントに「Claude Code」または「Developer」ロールがない、企業プロキシがリクエストへ干渉している、のいずれかです。プラットフォームAPIでは、403 - permission_errorはキーにそのリソースへの権限がないことを意味し、組織とワークスペースの設定に照らして判定されます。また、complianceエンドポイントでは、スコープが不適切な有効キーに対しては仕様上401ではなく403が返ります。
FAQ
Claude APIのエラーコード403は何を意味しますか?
意味は2通りあります。permission_errorなら、そのキーには対象リソースへの権限がありません。組織またはワークスペースの設定を確認します。一方、ログイン後のRequest not allowedは、サブスクリプション状態、Consoleロールの不足、プロキシのいずれかを示します。
401はリトライする価値がありますか?
401だけを理由にリトライしても意味はありません。拒否された認証情報や認証情報なしのリクエストは、次回も同じ結果になります。バックオフ前にRetry-Afterを守る必要がある429とは異なります。自然に解消する可能性がある401はapiKeyHelperのケースで、Claude Codeはエラーを報告する前にすでに追加で2回リトライします。