Troubleshooting · 許可權

403 Forbidden — Key 是好的,而這正是問題所在

30 秒版
  1. 先別換 Key——403 表示身份已經透過了,換新 Key 帶的是同一個賬號的同一套許可權,必然無效。
  2. 憑證不動,只換模型:換一個最基礎的模型再發一次。基礎模型 200、目標模型 403 = 許可權問題,兩次請求三十秒定位。
  3. 每個模型都 403 且和地區相關 = 地區限制;時有時無、只在某些請求上 = 端點側策略攔截。

403 表示伺服器讀了你的憑證、接受了它、算出了你是誰,然後還是拒絕了這個請求。身份那一關早就過了。所以所有衝著身份去的處置——重新生成 Key、重貼一次、換一種頭部寫法、重灌客戶端——保證不會有任何變化。403 問的不是「你是誰」,它回答的是「你不可以做這件事」,而這個答案不在你的 Key 裡面。

看到 403 就去換 Key,是這一類錯誤裡最常見、也最浪費時間的一步。它花掉一小時,而且不可能成功:同一個賬號重新簽發的 Key,帶的是一模一樣的許可權。換 Key 不是修,是把同一個實驗再跑一遍。

你看到的報錯

以下任意一種
API Error: 403 {"type":"error","error":{"type":"permission_error","message":"You do not have permission to use this model"}}

403 Forbidden

{"error":{"message":"You do not have permission to access this resource","type":"permission_error","code":"403"}}

<html><head><title>403 Forbidden</title></head>

第四種比它看起來更重要。一個沒有 JSON body 的 HTML 403,說明 API 根本沒看到你的請求——是它前面的某一層在邊緣就把你攔掉了。而帶 "type":"permission_error" 的 JSON body,說明 API 確實看到了、看懂了,然後說不行。這是兩個不同的問題,負責的人也不同。

401 和 403 — 這個分辨決定了後面所有動作

這兩個常被當成同一個問題處理,而正確的做法幾乎是相反的。

401 Unauthorized403 Forbidden
伺服器做的判斷它不知道你是誰它清楚知道你是誰
你的憑證被拒、缺失,或格式不對被接受了
換一把新 Key 有用嗎有用,前提是舊的那把真的壞了沒用 — 同一個賬號,同一份許可權
什麼因素會改變結果憑證你要的那個模型、地區、功能,或賬號狀態
該修的地方在你的配置賬號,或運營方的策略
該看哪一頁401 Unauthorized本頁

不確定手上是哪一種,就看響應 body,不看狀態行——見下面第 1 步。端點自己也不總是前後一致。

403 的三種來源

它們在響應里長得幾乎一樣,行為卻完全不同。「指紋」那一列,就是你不用問任何人也能分辨的方法。

從哪來訊息的樣子指紋誰能修
① 許可權沒開賬號沒有那個模型或那個功能會點名某個具體模型或功能跟模型繫結——同一份憑證換一個更基礎的模型就成功賬號持有人,或運營方
② 地區或合規限制請求從哪裡發出泛泛而談,有時會提到地區或國家跟位置繫結——通常是每一個模型都失敗,形態相同,每次都一樣運營方;你的配置碰不到
③ 端點側策略攔截併發限制、內容策略、賬號狀態泛泛而談;常是沒有 JSON body 的 HTML跟請求或時間繫結——換一個提示詞,或過一陣子再試就會成功你(改請求),或運營方

注意這三種指紋的共同點:沒有一個跟 Key 有關。三種都是靠改動憑證以外的東西、再看結果變不變來分辨的。

按這個順序排查

1 · 先確認它真的是 403,而且要讀 body 不讀狀態行#

終端
# <模型 id>:從 https://api.9coding.com/v1/models 裡複製一個
curl -sS -o /tmp/403body.txt -w 'status=%{http_code}\n' https://api.9coding.com/v1/messages \
  -H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "content-type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model":"<模型 id>","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'
cat /tmp/403body.txt

type 這個欄位,不要看那個數字:

  • "type":"permission_error" → 是真正的 403,繼續看本頁。
  • "type":"authentication_error" → 狀態碼和 body 對不上。以 body 為準。這是一個披著 403 外衣的憑證問題,請看 401 Unauthorized
  • 完全沒有 JSON body — 是 HTML 或是空的 → 你在 API 那一層之前就被攔掉了,屬於 ③ 的邊緣側。跟你的模型、你的 Key 都沒有關係。
  • 連狀態碼都沒拿到 → 這從來就不是 403,請看連線錯誤

2 · 分開 ① 和 ②③ 的關鍵測試 — 換模型,不換憑證#

整個手法就是這一句:把憑證固定住,去改 403 真正在管的那個東西,讓兩個結果自己告訴你是哪一種來源。

終端
# <模型 id>:從 https://api.9coding.com/v1/models 裡複製一個
for M in "<模型 id>" "<同一端點上一個更基礎的模型>"; do
  printf '%s -> ' "$M"
  curl -sS -o /dev/null -w '%{http_code}\n' https://api.9coding.com/v1/messages \
    -H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
    -H "content-type: application/json" \
    -H "anthropic-version: 2023-06-01" \
    -d "{\"model\":\"$M\",\"max_tokens\":16,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}"
done
基礎模型你要的模型你學到了什麼
200403① 許可權沒開。憑證有效、賬號正常、網路沒問題,你就是沒有那個模型。你機器上任何配置都變不出來
403403跟模型無關 → 是 ② 或 ③,去第 3 步
401401走錯頁了,請看 401 Unauthorized
200404 / not_found_error有些端點會把「你不能用的模型」報成找不到而不是被禁止,以免透露有哪些模型存在。請看 Model not available(某個端點用哪一種形態並不統一,按你自己的端點確認。)

第一行是多數人從來沒走到的結果,因為他們在「403,一定是 Key 的問題」那裡就停下來去換 Key 了。兩個請求、三十秒,你就知道要不要動自己的配置。

3 · 是這個請求的問題,還是這個賬號的問題?(③)#

模型不變,把請求砍到最精簡。

終端
# <模型 id>:從 https://api.9coding.com/v1/models 裡複製一個
curl -sS -o /dev/null -w '%{http_code}\n' https://api.9coding.com/v1/messages \
  -H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "content-type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model":"<模型 id>","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'
  • 光一句 hi 能成功,你真正的請求回 403 → 賬號是好的,是請求裡面的某個東西踩到了策略:內容、體積、某個工具或功能被攔、某個不支援的引數。用二分法拆請求:先去掉 system prompt,再去掉 tools,再去掉附件——一次只去掉一樣,每去掉一樣就重跑一次。讓 403 變回 200 的那一步就是答案。
  • 最精簡的請求也回 403 → 內容完全不是重點,去第 4 步。
還有一個很可靠的時間線索:許可權不會忽有忽無。一個隨著負載時好時壞、或者停一下就恢復的 403,是 ③(併發或濫用防護),不是 ①。只要它是間歇性的,就別再查許可權了。

4 · 是「你在哪裡」的問題,不是「你是誰」的問題嗎?(②)#

同一份憑證、同一個模型,換一個網路位置再跑一次。

  • 別的地方通、這裡不通 → ② 地區或合規限制。
  • 地區限制的形狀很好認:它對每一個模型都一樣每次都能復現,而且對你配置裡的任何東西都沒有反應。對照一下:① 是挑模型的,③ 是間歇的。
  • 認它要看行為,不要看響應長什麼樣。位置層面的拒絕,可能由 API 以 JSON 返回,也可能是更前面的邊緣層直接回一段 HTML、甚至什麼都不回——形態是變的,不能當判據。能當判據的是:在這個網路上,它對所有東西、每一次都失敗。

② 在你機器上沒有東西可以修;而某個限制能不能繞過,是服務條款的問題,不是技術問題。花時間之前,先看運營方的條款。

5 · 確認這份憑證到底屬於哪個賬號#

許可權掛在賬號、組織或專案範圍上,不掛在你環境變數裡那串字上。兩把看起來可以互換的 Key,可能帶著完全不同的許可權。

終端
env | grep -i anthropic

每一條都要看,不要只看你記得設過的那條,專案或工作區級別的配置檔案也要一起看。有一種很常見的版本:同一把 Key 在某個專案能用、在另一個專案回 403,因為第二個專案有自己的配置檔案,指向另一個賬號。變數不是 Key,是它背後那個賬號。

什麼時候這不是你的問題

以下情況說明問題在賬號或運營方的策略,你這臺機器上沒有東西要修:

  • 今天早些時候還能用、你沒改任何配置,而現在每一個模型都回 403,
  • 同一個賬號下的其他客戶端也是一樣的錯,
  • 響應 body 是 HTML 不是 JSON,說明 API 根本沒收到請求,
  • 或者失敗是跟著負載走,不是跟著你要的東西走。

依次去看運營方的狀態頁、變更日誌,以及你的賬號或賬務狀態。一個既不公開狀態頁也不公開變更日誌的端點,是你從外面沒辦法排錯的端點——在你把事情押上去之前,這件事值得先知道。我們的在狀態頁

提問題時請帶上 request id——9Coding 的報錯響應裡都有一個,形如 (request id: 2026...)。這個 id 加上時間戳、模型名和完整報錯文字,就能把那一次呼叫查出來。只說「用不了」的反饋沒法排查。

不要做的事

下面每一件都是衝著身份去的,而身份那一關早就過了:

  • 不要重新生成 Key。同一個賬號、同一份許可權、同一個 403。
  • 不要在 ANTHROPIC_API_KEYANTHROPIC_AUTH_TOKEN 之間換來換去。那是 401 形狀的處置;如果換了有效,你手上的本來就是 401。接 9Coding 時設 ANTHROPIC_AUTH_TOKEN,Key 以 sk-9c- 開頭,ANTHROPIC_BASE_URL 就是 https://api.9coding.com,結尾不要斜槓,不要帶 /v1
  • 不要寫迴圈重試。403 是一個決定,不是一個隊伍。重試對 429 和 529 是對的,在這裡沒有意義——只有 ③ 是例外,而且那時候「隔一段時間再試一次」是診斷手段,不是修法。
  • 不要重灌客戶端。這個決定是伺服器做的,客戶端只是把它列印出來。

相關報錯