Claude API 401 报错:先别急着换 Key,从这三处查起

401 不是账号被封,是身份没被认出来

先认准报错原文属于哪一种

401 的含义只有一句话:请求打到了服务端,服务端不知道你是谁。走 OpenAI 兼容协议时,响应体通常是这样的:

{"error":{"message":"Incorrect API key provided: sk-abc***xyz","type":"invalid_request_error","code":"invalid_api_key"}}

走 Anthropic 原生协议时,响应体是这种形态:

{"type":"error","error":{"type":"authentication_error","message":"invalid x-api-key"}}

看到 invalid x-api-key,说明服务端压根没收到你的 Key;看到 Incorrect API key provided 并带出一段掩码,说明 Key 收到了但内容对不上。这两句话指向完全不同的位置,前者去查请求头,后者去查 Key 字符串本身。

一个动作分辨「Key 错」还是「头错」

把 Key 原样抄进 curl,发一个最小请求。通了,说明你的代码或客户端在组装请求时动了手脚;不通,说明 Key 本身有问题。

curl -i https://xyuapi.top/v1/chat/completions \
  -H "Authorization: Bearer sk-你的完整Key" \
  -H "Content-Type: application/json" \
  -d '{"model":"claude-sonnet-4-5-thinking","max_tokens":256,"messages":[{"role":"user","content":"ping"}]}'

curl 通、代码不通,排查范围立刻从「Key 和账号」缩小到「你的请求头」。这一步能省掉一半以上的无效操作。

401 出现了也不要去充值

401 和余额没有关系。余额不足会返回 402 或额度相关的提示,权限不够返回 403,请求过快返回 429。看到 401 就充值、就换 Key、就重装客户端,是把时间花在了不相干的环节上。

状态码服务端的真实意思你该先动哪里
401没认出你是谁鉴权头格式、Key 字符串、base_url
403认出来了,但没权限模型是否可用、Key 是否被限制
402认出来了,额度不够充值或换 Key
429认出来了,请求太快并发、退避重试
404路径不对base_url 是否带 /v1

鉴权头写错,是 401 的头号原因

Anthropic 原生 SDK 默认发 x-api-key

Anthropic 的协议用 x-api-key 传 Key,路径是 /v1/messages。很多人把 base_url 改成聚合地址,却继续用 Anthropic SDK,SDK 仍然发 x-api-key。如果这一侧只认 Authorization: Bearer,结果就是 invalid x-api-key。这类 401 不是 Key 错,是头名字没对上。

OpenAI 兼容协议必须走 Bearer

OpenAI 生态的客户端(Chatbox、Cherry Studio、NextChat、Cline、Cursor)统一发 Authorization: Bearer sk-xxx,路径是 /v1/chat/completions。用这些客户端时,鉴权头不用你操心,但 Key 的填法和 base_url 的结尾一个都不能错。

两套协议的头和路径对照

项目Anthropic 原生协议OpenAI 兼容协议
base_urlhttps://api.anthropic.comhttps://xyuapi.top/v1
鉴权头x-api-key: sk-xxxAuthorization: Bearer sk-xxx
路径/v1/messages/v1/chat/completions
system 提示独立字段放进 messages 的第一条
max_tokens必填可选

同一套 Key、同一个模型,换协议就要换头和路径,三样必须成套,混搭就是 401 或者 400。

base_url 少一段或多一段,一样会 401

正确的地址只有两种写法

https://xyuapi.top/v1(主入口)和 https://xyuai.cc/v1(备用)。客户端里只需要填到 /v1 为止,后面的 /chat/completions 由客户端自己拼。多填一段就变成 /v1/chat/completions/chat/completions,服务端匹配不到路由,有些网关会直接回 401 而不是 404。

结尾的斜杠和空格同样致命

https://xyuapi.top/v1/ 这种结尾多一个斜杠,多数客户端能容错,但少数会把斜杠和后续路径拼成双斜杠 //chat/completions,触发鉴权中间件的路径白名单失败,返回 401。填地址时把结尾斜杠和首尾空格一起删掉,是两秒钟的事。

路径的大小写不能随手改

主机名不区分大小写,路径区分。/V1/v1 在不同实现下结果不同,有的回 404,有的回 401。填地址时用一个确认能通的模板复制过去,不要手工敲,也别在中间加空格。

Key 字符串本身的坑:空格、引号、换行

从网页复制 Key 最常带三个字符

从后台页面复制时,尾部很容易带上一个空格或者换行。肉眼看不出来,但服务端算出来的是另一个字符串。验证方法:打印 len(key)repr(key),Key 的前后只要有引号内容多出来,立刻就能看到。还要注意两点:有的 Key 带 sk- 前缀,有的不带,复制时不要把前缀漏掉或者多补一个;有的页面会把 Key 中间做省略显示,显示出来的那串是残缺的,必须用复制按钮取完整值。

.env 文件里的引号陷阱

.env 时给值加上引号,某些加载库会把引号一起读进去,于是 Key 变成 "sk-xxx"。读取时 strip 掉两端的空白和引号,是最省事的防御。

import os
from dotenv import load_dotenv

load_dotenv()
key = (os.getenv("API_KEY") or "").strip().strip('"').strip("'")
assert key, "API_KEY 没读到"
print("len =", len(key), "head =", key[:6] + "***" + key[-4:])

环境变量没生效的三种情形

改了 .env 但程序读的是系统环境变量,同名时优先级要看加载顺序;在 Docker 里运行,容器读不到宿主机的变量,要在 compose 里显式声明;在 IDE 里配了运行配置,命令行终端却还是旧值。三种情形的外观都一样:代码里写的是新 Key,实际发出去的是旧 Key,于是 401。

令牌和模型授权是两件事

后台里的令牌负责身份,模型授权负责范围。令牌被停用、设了有效期、或者被限制只能调某几个模型时,表现出来不一定是 403:有的实现在找不到可用模型时会先从鉴权环节返回,回一个 401。判断方法是拿同一个令牌去调另一个模型,换了模型就正常,说明令牌本身没问题,是原来那个模型的授权范围没覆盖到。

401 之前先看令牌有没有过期

令牌带有效期时,过期那一刻起所有请求都会 401,代码一个字没改也会突然全挂。这类问题的特征很明显:昨天还好好的,今天百分之百失败。遇到这种形态,先去后台看令牌状态,再回头看代码,顺序反了会白查很久。

客户端里那些看起来不像 401 的错误

Key 填到了错误的位置

Chatbox、Cherry Studio 这类客户端有两个输入框:模型名称和 API Key,旁边还有服务地址。把 Key 填进模型名称框,请求头里就没有 Key,服务端回 401,客户端往往只弹一句「请求失败」。先看一遍三个输入框的对应关系,再排查网络。

需要开启 OpenAI 兼容模式的客户端

Cline、Cursor、Continue 这类编辑器插件默认走自家服务,切到自定义地址时要在设置里选 OpenAI Compatible 之类的模式,并把 base_url 指到 /v1。模式没切,插件把请求发到原来的服务上,返回的 401 跟你填的 Key 毫无关系。

环境变量名对不上

不同 SDK 读的变量名不一样:有的读 OPENAI_API_KEY,有的读 ANTHROPIC_API_KEY,有的读 API_KEY。名字写错,SDK 就当没配 Key,直接抛鉴权异常,连请求都不会发出去。这种情况日志里通常看不到任何 HTTP 请求记录,是一个很好用的判据:没有请求记录 + 401,基本就是本地没读到 Key。

浏览器和中间层造成的 401

前端直连会先在预检阶段失败

在浏览器里用 fetch 直连接口,跨域预检如果不放行 Authorization 头,请求根本到不了服务端,控制台报的是 CORS 相关错误。把调用放到自己的后端去发,是唯一稳妥的做法,前端不要持有 Key。

反向代理会吞掉鉴权头

自己加了一层 nginx 或者网关时,如果开了某些头过滤规则,Authorization 可能被丢掉;用 _ 拼接的自定义头也会被默认丢弃。判断办法很直接:在服务端日志里看有没有收到鉴权头,收不到就是中间层的问题。

容器和云函数的变量注入

云函数、CI 环境里环境变量是在配置面板里配的,改完要重新部署才生效。本地跑得好好的,一上云就 401,先确认线上那份配置到底有没有这个变量。

一套能自己找出 401 的检查代码

把真实请求头打出来

关键字是「真实」:不要打印你打算发的头,要打印最终发出去的头。

import requests
from requests import Session

s = Session()
req = requests.Request(
    "POST",
    "https://xyuapi.top/v1/chat/completions",
    headers={
        "Authorization": "Bearer " + key,
        "Content-Type": "application/json",
    },
    json={"model": "claude-sonnet-4-5-thinking",
          "max_tokens": 256,
          "messages": [{"role": "user", "content": "ping"}]},
)
prepared = s.prepare_request(req)
safe = dict(prepared.headers)
safe["Authorization"] = safe.get("Authorization", "")[:14] + "***"
print(prepared.method, prepared.url)
print(safe)

resp = s.send(prepared, timeout=(10, 300))
print(resp.status_code, resp.text[:300])

注意 resp.text[:300] 这一步:401 的响应体里通常写着到底哪一项不对,直接 raise_for_status() 会把这段唯一有用的文字丢掉。

Node 里的最小验证

node -e "fetch('https://xyuapi.top/v1/chat/completions',{method:'POST',headers:{'Authorization':'Bearer '+process.env.API_KEY,'Content-Type':'application/json'},body:JSON.stringify({model:'claude-sonnet-4-5-thinking',max_tokens:64,messages:[{role:'user',content:'ping'}]})}).then(async r=>console.log(r.status,await r.text()))"

三个结果对照着看

curl 的返回、代码打印的真实请求头、后台的令牌状态,三样摆在一起看。三者一致还报 401,问题在请求路径上,去核对 base_url 和客户端模式;三者不一致,问题在配置传递环节,去核对读取顺序和缓存。这一步能把范围收窄到具体一段代码。

日志里只留脱敏片段

排查完记得把 Key 从日志里摘出去,只留前六位和后四位。401 排查过程中很容易把完整 Key 贴进聊天窗口或者工单系统,那是比 401 更麻烦的事。

为什么改了代码还是 401

客户端缓存了旧的令牌

桌面客户端和编辑器插件都会把配置写在本地。你换了令牌,但只改了其中一处,界面上还留着一份旧的,实际发出去的是旧值。改完配置重启一次客户端,比反复读代码快得多。

进程没有重启,环境变量还是旧值

改完 .env 之后,正在运行的进程读的还是启动那一刻的值。开发服务器带热重载时,部分文件变化并不会触发进程重启,环境变量依旧。判断方法是在代码里打印一次读到值的前六位,和后台显示的对照。这一步只要十秒,能省掉半小时的自我怀疑。

多份配置互相覆盖

同一个项目里同时存在 .env.env.local 和系统环境变量三份配置时,谁生效取决于加载顺序。不要靠猜,把程序真实读到的值打出来,是唯一可靠的做法。

换到聚合接入时的填法和计费

一个 Key 调全系列模型怎么填

小鱼API 是 AI API 接入平台,走 OpenAI 兼容协议。客户端里填 https://xyuapi.top/v1(备用 https://xyuai.cc/v1),Key 填后台生成的令牌,模型名直接写模型 ID,例如 claude-sonnet-4-5-thinking。一个 Key 可以调全系列模型,不需要为每个模型单独配一遍。按次计费,一次请求一个固定价,输入长短不影响价格。

模型单次价格
gemini-2.5-pro0.031 元/次
deepseek-v3.2-thinking0.049 元/次
claude-sonnet-4-5-thinking0.09 元/次
claude-opus-4-5-thinking0.12 元/次

排查完还报 401 就照这张清单走

顺序检查项通过标准
1curl 最小请求返回 200 和正常补全
2真实请求头只有一条 Authorization,值以 Bearer 开头
3base_url/v1 结尾,无多余斜杠和空格
4Key 字符串长度符合预期,前后无空白和引号
5环境变量程序读到的 Key 与后台显示的一致
6客户端模式已切到 OpenAI 兼容模式

六项走完还是 401,把 curl 的完整命令和响应体一起拿出来,问题一定在这两段文字里,不在别的地方。

相关阅读

🚀 想要立即使用?来小鱼API体验全系列AI模型

查看全部产品

支持Gemini / Claude / GPT / Grok / DeepSeek · 国内直连 · 支付宝/微信