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 是对的,在这里没有意义——只有 ③ 是例外,而且那时候「隔一段时间再试一次」是诊断手段,不是修法。
  • 不要重装客户端。这个决定是服务器做的,客户端只是把它打印出来。

相关报错