看到 401,代表伺服器拒絕了某項憑證;這不等於 API key 字串一定打錯。同樣地,403 也未必表示權限不足。兩家供應商都會在憑證完全有效的某些情況下回傳這些狀態碼。不過有一個例外應先檢查:若錯誤訊息回顯的遮罩金鑰,其首尾字元和你手上的金鑰對不上,表示真正送到伺服器的並不是你以為送出的那組憑證。
看似一樣的 401,其實有三種原因
以下每種情況都會回傳 HTTP 401 與驗證錯誤類型,但處理方向剛好相反。表中的訊息來自我以刻意無效的金鑰,對兩個端點發出的六次請求;無效憑證會在授權程序啟動前就被拒絕,因此不需要真實金鑰也能重現。
| 實際發生的狀況 | 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 |
| 憑證送到錯誤的 header | Invalid bearer token | Missing bearer or basic authentication in header,與上一列完全相同 |
Anthropic 會明確區分這三種情況。OpenAI 端點則把「沒送憑證」和「送到它不讀取的 header」視為同一種錯誤。因此,透過 relay 的使用者即使明明已在 shell 設定金鑰,仍可能盯著 missing-header 錯誤百思不得其解。
回應 header 無法消除上述歧義,但能區分「憑證被拒絕」與「憑證從未進入授權流程」。請實際跑完這六個呼叫並看 header,不要只看錯誤文字:
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
以下是 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
在這兩個端點上,被拒絕的憑證會帶有供應商自己的授權欄位,且不會出現 challenge header;未進入授權流程的請求則剛好相反。重複請求仍得到相同分界,但這只是針對兩個端點、具日期性的觀察,並非文件保證的行為。請自行重跑上方指令,不要假設你的供應商也完全相同。若落在「已送達但遭拒絕」這一列,請前往 OpenAI 或 Claude 的金鑰頁面,依序確認四件事:值的開頭或結尾是否有空白、金鑰是否已被刪除或撤銷、金鑰是否屬於不同於目前呼叫目標的 project 或 organization,以及客戶端是否仍快取舊副本。前三項都確認無誤後,再考慮重新產生金鑰。
CLI 到底送出了哪一組憑證?
如果 coding CLI 回報你從未設定過的 invalid key,通常不是快取問題,而是有其他憑證的優先權更高。Claude Code 會按固定順序解析六種來源;這套官方記載的優先順序,也決定各來源會使用哪個 header 傳送。
| 優先順序 | 來源 | 送出的 Header |
|---|---|---|
| 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 官方的排查順序是:先在啟動 CLI 的 shell 中執行 env | grep ANTHROPIC,再執行 /status;若原本是要使用訂閱,便取消設定該變數。env 只能檢查表中的第 2、3 列,而 /status 才會顯示最終解析出的來源是否為雲端供應商、helper 指令碼或登入狀態。
你發誓沒設定過的那組憑證
這則回報很能說明問題的樣貌:
"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"
維護者首先詢問是否設定了 apiKeyHelper。答案是有,而它的完整輸出只有 PLACEHOLDER_NOT_IMPLEMENTED_ON_MAC_YET。如果 helper 回傳垃圾內容、以非零狀態結束,或完全沒有輸出,就會送出一組佔位憑證;API 因此回傳 401,描述的是金鑰問題而非指令碼。Claude Code 現在會在三次嘗試內明確回報這個失敗原因;同一條目也記載,在 v2.1.208 之前,helper 失敗約經過十次靜默重試後,只會顯示通用的 401。
環境變數也可能在你不知情時被設定。Anthropic 在文件中列出 direnv、dotenv shell 外掛與 IDE 終端機,這些工具都可能從專案的 .env 載入過期金鑰。helper 也會在五分鐘後,或收到 HTTP 401 時再次執行,因此故障的 helper 會定時重新出現。
Base URL 指向 gateway 時,誰在驗證?
若 ANTHROPIC_BASE_URL 指向 LLM gateway,401 後面的文字是 gateway 的訊息,不是 Anthropic 的訊息,/login 也無法改變結果。反過來說,指向 relay 的 OpenAI 相容客戶端也是一樣。以下是一份 Codex 回報,其錯誤字串結尾為 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"
清除本機憑證解決不了這個問題,因為由 base URL 決定誰來判斷請求。憑證必須對應該端點背後的服務。Claude Code 文件說明,對於使用 bearer token 驗證的gateway,應使用 ANTHROPIC_AUTH_TOKEN;若把同一個值放進 ANTHROPIC_API_KEY,它會改用 X-Api-Key 送出,也就是上表的第 3 列。其他客戶端有各自的規格,應確認它實際送出了哪個 header。Claude Code on the Web 是例外:它永遠使用訂閱憑證,在 sandbox 中設定任一變數都不會覆寫該設定。
API key 沒問題,仍可能拿到 403 的情況
本節情境並非第一手重現,因為每一種都需要特定帳戶狀態或地理位置;不過供應商文件已明確指出,有效金鑰確實可能回傳 403。
OpenAI 的錯誤碼參考文件列出 403 - Country, region, or territory not supported。這是地理區域檢查,與金鑰本身無關。往上兩列則是相反的情況:401 - IP not authorized,當請求 IP 不在 project 或 organization allowlist 時就會發生。金鑰有效,無效的是呼叫端。
若已成功登入卻出現 API Error: 403 {"error":{"type":"forbidden","message":"Request not allowed"}},Anthropic 在文件中列出三個原因,沒有一個是金鑰輸入錯誤:Pro 或 Max 訂閱未啟用、Console 帳戶缺少「Claude Code」或「Developer」角色,或企業 proxy 干擾請求。在 platform API 上,403 - permission_error 代表金鑰沒有存取該資源的權限,須檢查 organization 與 workspace 設定;而在compliance endpoints,有效金鑰若 scope 不符,設計上會回傳 403 而不是 401。
常見問題
Claude API 的 403 錯誤碼代表什麼?
可能是兩件不同的事。permission_error 表示你的金鑰沒有該資源的存取權,這取決於 organization 或 workspace 設定。登入後出現 Request not allowed,則通常指向訂閱狀態、缺少 Console 角色,或 proxy 問題。
401 值得重試嗎?
單靠重試不值得。憑證被拒絕或根本未送出,下次請求仍會得到同樣結果;這與 429 不同,後者的處理方式是先遵守 Retry-After,再進行退避重試。唯一可能自行恢復的 401 是 apiKeyHelper 情況,Claude Code 已會在回報前額外重試兩次。