403 Forbidden — Key 是好的,而這正是問題所在
- 先別換 Key——403 表示身份已經透過了,換新 Key 帶的是同一個賬號的同一套許可權,必然無效。
- 憑證不動,只換模型:換一個最基礎的模型再發一次。基礎模型 200、目標模型 403 = 許可權問題,兩次請求三十秒定位。
- 每個模型都 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 Unauthorized | 403 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
| 基礎模型 | 你要的模型 | 你學到了什麼 |
|---|---|---|
| 200 | 403 | ① 許可權沒開。憑證有效、賬號正常、網路沒問題,你就是沒有那個模型。你機器上任何配置都變不出來 |
| 403 | 403 | 跟模型無關 → 是 ② 或 ③,去第 3 步 |
| 401 | 401 | 走錯頁了,請看 401 Unauthorized |
| 200 | 404 / 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 步。
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_KEY和ANTHROPIC_AUTH_TOKEN之間換來換去。那是 401 形狀的處置;如果換了有效,你手上的本來就是 401。接 9Coding 時設ANTHROPIC_AUTH_TOKEN,Key 以sk-9c-開頭,ANTHROPIC_BASE_URL就是https://api.9coding.com,結尾不要斜槓,不要帶/v1。 - 不要寫迴圈重試。403 是一個決定,不是一個隊伍。重試對 429 和 529 是對的,在這裡沒有意義——只有 ③ 是例外,而且那時候「隔一段時間再試一次」是診斷手段,不是修法。
- 不要重灌客戶端。這個決定是伺服器做的,客戶端只是把它列印出來。
相關報錯
- 401 Unauthorized — 兩層認證,一樣的報錯資訊
- Model not available — 當答案回的是 404 而不是 403
- 429 Too Many Requests 與 529 Overloaded — 真的會自己解除的那一類限制
- 連線錯誤 — 當你連狀態碼都沒拿到
- 全部報錯速查 — 排查手冊總表