API 报 401 Incorrect API key / invalid_api_key / authentication_error 怎么解决?两分钟定位法(2026)
401 和模型、余额、限流基本无关:请求在核对身份这一步就被拒了。先用一条 curl 判断是「key 坏了」还是「key 没送到」,再按症状表逐项排查。
1. 先弄清 401 在说什么:请求在「认人」这一步被拦了
一次 API 请求在服务端大致要过几关:先核对身份(你是谁),再看权限(你能不能用这个资源),然后才是计费、限流,最后才轮到模型干活。401 停在第一关,所以它和模型名、提示词长短、余额多少基本无关 —— 换模型、缩短提示词都不会让 401 消失。Anthropic 官方错误文档里写得很清楚:401 对应 `authentication_error`,含义是 API key 有问题,例如格式不对、已被撤销或已过期;而 403 `permission_error` 是 key 没有使用该资源的权限,402 `billing_error` 是账单或付款信息有问题,429 `rate_limit_error` 是触发了限流。这几个状态码说明请求卡在了不同的关口,看清是哪个再动手,比反复换 key 有效得多。OpenAI 的报错文字和分类以它的官方错误码页面当前说明为准(入口:platform.openai.com/docs/guides/error-codes)。
2. 第一刀:是「key 坏了」还是「key 没送到」
很多人遇到 401 的第一反应是去控制台再建一个 key,建完还是 401,就开始怀疑服务商。更快的办法是先把问题切成两半:在终端里,不经过你的程序,用同一个 key、同一个 base_url 发一条最小请求。curl 也报 401 —— 问题在 key 本身,或者 key 和这个端点不是一家;curl 能通、程序报 401 —— key 没问题,问题在程序读 key 的方式、环境变量、或客户端里保存的旧配置。这一刀下去,排查范围直接减半。注意测试要用一条真正需要鉴权的请求:有些端点的模型列表接口是公开的,不带 key 也能返回,拉到列表不能证明 key 有效。
# OpenAI 兼容协议:鉴权头是 Authorization: Bearer
curl -sS https://cocodot.co/api/ai/v1/chat/completions \
-H "Authorization: Bearer $MY_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"claude-sonnet-5","messages":[{"role":"user","content":"ping"}],"max_tokens":16}'
# Anthropic 协议:官方写法是 x-api-key + anthropic-version
curl -sS https://cocodot.co/api/ai/v1/messages \
-H "x-api-key: $MY_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"claude-sonnet-5","max_tokens":16,"messages":[{"role":"user","content":"ping"}]}'3. key 本身:残缺、隐形字符和没换掉的占位符
「key 明明是对的」时,常见的真相是:你看到的 key 和程序发出去的 key 不是同一串字符。下面三种情况很常见:① 复制时少了开头或结尾几位(控制台里 key 往往只显示一次,复制不全就只能重建);② 首尾混进了空格、换行或引号 —— 从聊天软件、文档、配置文件里转抄时最容易发生,肉眼看不出来;③ 示例代码里的占位符(比如 `sk-YOUR_KEY`)没换掉,或者换到了另一个文件里。自查方法是打印 key 的指纹而不是全文:只输出长度、首尾是否含空白、是否含换行的布尔值,不输出任何密钥片段。长度和控制台里对不上、首尾多出空白,问题就找到了。别把完整 key 打到日志或截图里 —— 一旦 key 出现在公开的地方,最稳妥的做法是直接作废重建。
import os
k = os.environ.get("MY_KEY", "")
print("len =", len(k))
print("outer_whitespace =", k != k.strip())
print("contains_newline =", "\n" in k or "\r" in k) # 能看到 '\n'、空格、引号4. 最隐蔽的一类:key 和 base_url 不是同一家
key 只在发给它的那一家时有效。A 服务商的 key 发到 B 服务商的地址,对方的鉴权系统里根本没有这串 key,只能回 401。这类问题特别迷惑,因为 key 本身完全正常、在原来的地方还能用。典型场景:① 用 OpenAI 或 Anthropic 的官方 SDK 接第三方端点,但 base_url 没设上 —— 多数 SDK 不指定 base_url 时会走官方默认地址,于是第三方的 key 被送到了官方;② 反过来,官方 key 填进了只认自家 key 的中转或聚合服务;③ 在工具里换了服务商,只换了 key 没换地址,或者只换了地址没换 key。判断方法很简单:在程序里打印实际发出的请求地址,确认它和 key 来自同一家。以 cocodot 为例,OpenAI SDK 和客户端的 base_url 填 `https://cocodot.co/api/ai/v1`,Anthropic SDK 填 `https://cocodot.co/api/ai`(不带 /v1),key 在控制台创建、以 cocodot- 开头 —— 两个地址只差结尾的 /v1,填混通常表现为 404,而 key 和地址不是一家才是 401。
5. 请求头写法:Bearer 还是 x-api-key
不同协议放 key 的位置不一样。OpenAI 兼容协议用 `Authorization: Bearer <key>`;Anthropic 官方文档里的请求用 `x-api-key: <key>`,并带上 `anthropic-version` 头。自己拼 HTTP 请求时常见的错误有:漏写 `Bearer ` 前缀、写成了 `Bearer Bearer xxx`(SDK 已经自动加了一次前缀,你又手动加了一次)、把 key 放进了 URL 参数或请求体、头名拼错。用官方 SDK 时一般不会写错头,自己用 requests / fetch 拼请求时更容易出错。有些兼容端点两种写法都接受 —— 比如 cocodot 的 /v1/messages 接口接受 x-api-key 或 Bearer —— 但别默认所有服务商都这样,以对方文档为准。
6. 环境变量:你以为换了新 key,程序拿的还是旧的
「curl 能通、程序报 401」时,很可能是程序读到的 key 和你以为的不一样。几个高发点:① 终端里 export 过一个旧 key,.env 文件里写的是新 key —— 很多 .env 加载方式默认不覆盖已经存在的环境变量,旧的赢了;② 改了 .env 或系统变量,但没重启 IDE、终端或开发服务器,进程里还是旧值;③ 代码跑在容器、CI 或云函数里,本地设的变量根本没带进去,读到的是空字符串;④ 同一个项目里有两处读 key 的地方(比如一个用 OPENAI_API_KEY、一个用自定义变量名),改了一处漏了另一处。处理办法就是第 3 节那段指纹打印:放在发请求的那行代码旁边打印一次,看到的才是真正发出去的值。
7. Claude Code、Cursor 等客户端里的 401
客户端工具里的 401 多数是配置叠加造成的:工具自己保存了一个 key,环境变量里又有一个,登录态里还有一个,最后生效的不是你刚改的那个。排查顺序:先确认工具实际在用哪个地址和哪个 key(看它的设置页或调试输出),再把多余的那套清掉、只留一套,然后完全退出工具再重开。以 Claude Code 接 cocodot 为例,按 /claude-code 页的步骤设两个环境变量即可:`ANTHROPIC_BASE_URL=https://cocodot.co/api/ai`,`ANTHROPIC_AUTH_TOKEN` 填你的 cocodot key;如果之前设过别家的 key 或地址,先在当前终端里清掉再启动。Claude Code 的完整配置见 /hub/claude-code,Cursor 接自定义地址见 /hub/cursor-china-claude-gpt-api。
8. 还是 401:作废重建,带上请求 ID 找支持
前面都核过还是 401,就别再和这个 key 较劲:在控制台作废它、新建一个,更新所有用到它的地方(包括服务器、CI 和同事的机器),再用第 2 节的 curl 验一次。仍然不行时联系服务商支持,附上请求 ID、请求时间和报错原文 —— Anthropic 官方文档说明每个 API 响应都带一个唯一的 `request-id` 响应头,报错体里也有同样的 `request_id` 字段,支持人员靠它能直接查到那次请求。如果 401 已经解决、又冒出别的错误,说明请求过了身份这一关,换对应的排查方向:模型名不对见 /hub/api-model-not-found-fix,限流和并发见 /hub/api-429-rate-limit-concurrency-china,Claude 繁忙见 /hub/claude-api-529-overloaded,上下文超长见 /hub/context-length-exceeded-fix,OpenRouter 地区限制见 /hub/openrouter-403-china-alternative,超时和连接失败的综合排查见 /hub/ai-api-errors-china-fix。
401 症状 → 根因 → 解法
| 症状 | 根因 | 解法 |
|---|---|---|
| 刚复制的新 key 也报 401 | key 复制残缺,或首尾带了空格、换行、引号 | 打印 key 的长度和首尾几位比对,从控制台重新复制 |
| curl 能通,程序里报 401 | 程序读到的是旧 key、空值或占位符 | 在程序里打印 key 指纹(不打全文),确认和 curl 用的是同一个 |
| 换了服务商之后开始 401 | key 是 A 家的,请求却发到了 B 家(base_url 没改或没生效) | key 和 base_url 必须来自同一家,打印实际请求地址确认 |
| 只有某个 SDK 或客户端报 401 | 请求头写法不对:该用 x-api-key 却用了 Bearer,或反过来 | 对照该端点文档的鉴权头写法,或换成它支持的写法 |
| 之前好好的,突然全部 401 | key 被撤销、删除或过期 | 控制台确认 key 状态,作废后新建一个并更新所有配置 |
| 报错类型是 403 / 402 而不是 401 | 身份认出来了,但权限或计费有问题 —— 不是 key 写错 | 按对应状态码排查,别反复换 key |