GPT API 报 401 认证失败?先跑这三条命令,别再瞎猜 Key 有问题

401 到底是什么:先把「没带钥匙」「钥匙不对」「钥匙被停用」分开

401 Unauthorized 只有一句话的意思:服务端没认出来你是谁。它跟你的网络、跟你的提示词、跟模型都没关系。看到 401,你现在该做的不是重启客户端,而是把请求原样丢给 curl 复现一次。

排查路径按这个顺序走,别跳步:先确认这把 Key 本身是活的 → 再确认 Key 传输过程中没被改脏 → 再确认 Authorization 头真的发出去了 → 最后才查客户端的 base_url 和额度权限。前两步能解决八成的 401。

401 的三张面孔

真正要分的是这三种,它们报错长得很像,处理方式完全不同:

三条命令,30 秒跑完再谈别的

先别改代码,先把这三条跑一遍。用的是小鱼API 主入口 https://xyuapi.top/v1

# 1. 最小验证:只验 Key,不带任何业务参数
curl -s -o /dev/null -w '%{http_code}\n' https://xyuapi.top/v1/models \
  -H "Authorization: Bearer $KEY"

# 2. 完整看响应体,401 的原文会告诉你它到底收到了什么
curl -i -s https://xyuapi.top/v1/models -H "Authorization: Bearer $KEY"

# 3. 故意带一个错的 Key,做对照组
curl -i -s https://xyuapi.top/v1/models -H "Authorization: Bearer sk-wrong-key-test"

第 1 条返回 200,说明 Key 是活的,问题在你的客户端或业务请求。返回 401,问题在 Key 或调用方式,继续往下看。第 3 条是对照组:拿错误的 Key 去撞,看错误长什么样,再跟你实际的报错比对,一眼就能判断你的请求到底有没有把 Key 带进去。

401、402、403、429 别混着查

这四种状态码经常被当成一回事,其实处理路径完全不一样:

状态码原文关键词真实含义你现在该做什么
401Incorrect API key / invalid_api_keyKey 没带、带错、或已失效跑上面第 2 条命令看原文
401You didn't provide an API key请求头压根没这个字段检查代码或客户端有没有真的加上 Authorization
403insufficient permissions / 无权访问Key 是真的,但这个模型或分组不允许换模型,或换对应分组的令牌
402insufficient quota / 余额不足账户余额不够充值,或换有额度的 Key
429Rate limit reachedKey 没问题,请求太密降并发、加退避

记住一句:403 是「我认识你,但你不能进这扇门」,401 是「我不认识你」。 这两个搞混,会白查半天。

第一步:确认这把 Key 本身是活的

用 /v1/models 做最小验证

/v1/models 是个不带业务参数的接口,正好拿来做认证测试。它只要 Key,不要提示词,也不消耗额度。你现在敲这条:

curl -s https://xyuapi.top/v1/models -H "Authorization: Bearer $KEY" | head -20

返回一长串模型 ID,就是 200,Key 没问题。返回 {"error":{"message":"Incorrect API key provided..."}},这个 Key 就是错的了。

把它打印出来的那串和你的实际值对一遍

服务端在报错里会把 Key 前后各截几个字符。比如它说 Incorrect API key provided: sk-abc***xyz,你就去你的配置里找 sk-abc...xyz对不上,说明你读的根本不是同一份配置——最常见的是改了一个 .env,程序读的是另一个,或者改了代码,跑的却是上一次构建的产物。

顺手把 Key 的长度数一遍

# macOS / Linux
echo -n "$KEY" | wc -c
# Windows PowerShell
$env:KEY.Length

平台上的 Key 长度是固定的。数出来多了 1 到 2 个字符,基本就是复制时带上了换行或空格;少了,就是复制不全,双击选中复制最容易漏掉首尾。这一步看着土,但「肉眼看着一模一样、实际字节不一样」这类问题,只有数长度才能发现。

第二步:揪出 Key 里看不见的脏字符

复制粘贴最常见的四种脏数据

  1. 尾部跟了一个换行。从网页或聊天窗口复制最容易带。
  2. 首尾被引号包住。.env 里写成 API_KEY="sk-xxx",程序把引号也读进去了。
  3. 前面已经带了 Bearer 。然后你在代码里又拼了一次 Authorization: Bearer ,变成 Bearer Bearer sk-xxx
  4. 文件开头的 BOM 或全角空格。用记事本另存过的 .env 很容易中这一条。

Linux / macOS 用 cat -A 看清楚

cat -A .env | grep API_KEY

正常应该是 API_KEY=sk-xxxxxxxx$。如果你看到 API_KEY=sk-xxxxxxxx^M$(Windows 换行)、API_KEY="sk-xxx"$(引号)、或者开头有个 M-oM-;M-?(BOM),病根就在这儿。

Windows 上用 PowerShell 看字符码

$k = $env:OPENAI_API_KEY
$k.Length
[System.Text.Encoding]::UTF8.GetBytes($k) -join ','

长度对不对、有没有可疑的非 ASCII 字节,一眼就看得出来。比如 226 开头的那串字节,多半就是被塞进去的全角空格;结尾冒出 13 或 10 的,就是换行符。这种字符在编辑器里完全看不见,但发给服务端就是错的,所以必须用命令看,不能靠眼睛。

一个能立刻排除掉一半可能性的做法

把 Key 直接硬写到测试脚本里跑一次。能通,说明 Key 本身没问题,问题全在配置读取链路;不通,问题在 Key 或调用方式。这个二分法比逐个猜配置快得多。

第三步:确认 Authorization 头真的发出去了

让服务端告诉你它收到了什么

把请求原样手打一遍,不过任何客户端:

KEY='sk-你的key'
curl -i -X POST https://xyuapi.top/v1/chat/completions \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gemini-2.5-pro","messages":[{"role":"user","content":"ping"}]}'

手打能通、程序里不通,问题百分之百在你的请求构造或中间层,不用再怀疑平台。

四种拼错的方式

你写成了这样结果修法
Authorization: sk-xxx没有 Bearer 前缀,多数网关直接判 401补上 Bearer ,注意后面有一个空格
Authorization: Bearer: sk-xxx冒号多了一个冒号只出现在字段名后面
Authorization: Bearer sk-xxx 尾部夹了空格去掉空格,用变量而不是手打
api-key / x-api-key各家客户端习惯不同走 OpenAI 兼容协议时统一用 Authorization: Bearer

两个字段名,别搞反

有些客户端界面上有「API Key」和「Bearer Token」两个输入框,或者把鉴权头字段名做成可填的。填错地方的结果就是请求里压根没这个头,服务端回你 You didn't provide an API key。看到这句原文,第一反应就该是「我的头没发出去」,而不是「我的 Key 坏了」。

中间层吞 header 的情况

如果你在代码和平台之间还挂了自建网关、Nginx 反向代理、或者公司网络出口的审计设备,Header 是可能被改写甚至丢掉的。做法很简单:把请求指向平台主入口直连一次,再指向你的中间层一次,两次结果不一样,问题就在中间层。

第四步:客户端配置的六个坑

base_url 到底填什么

OpenAI 兼容客户端里那个输入框要填的是/v1 为止

备用入口是 https://xyuai.cc/v1,规则一样。两个入口都能填,用哪个都行。

Chatbox、Cherry Studio 这类别把 Key 填错栏

这类客户端一般有「API 提供商」下拉和「API Key」「API 域名」两个框。顺序是:先把提供商切成「OpenAI 兼容」或「自定义」,再填域名,最后填 Key。先填 Key 再切提供商,有些客户端会把 Key 清空,然后你看到 401,以为是 Key 错了。

NextChat、LobeChat 部署版的环境变量

自部署版本常常是环境变量注进去的。变量名写错一个字母(OPENAI_API_KEY 写成 OPEN_AI_API_KEY),程序读不到就是空值,请求里自然没头。改完记得重启容器,环境变量不会热加载。

.env 里的引号和空格

API_KEY = sk-xxx 这种等号两边带空格的写法,在很多解析器里值会跟着带上空格。写成 API_KEY=sk-xxx,不加引号,不加空格。

代码里的占位符忘了替换

api_key = "YOUR_API_KEY" 这种模板代码,忘了替换就是 401,而且它的报错长得跟真 Key 错一模一样,很容易被骗。搜一下代码里有没有 YOUR_your-keyxxx 这类残留。

流式和非流式的差异

个别情况下非流式能通、流式 401,通常是客户端对 SSE 请求单独走了一套 header 拼装逻辑。遇到这种,先切回非流式确认 Key 没问题,再去查流式那条链路。

另外还有一种被误当成 401 的情况:客户端在正式请求之前会先发一个探测请求去拉模型列表,探测失败时界面上直接弹「认证失败」,但真实原因是那个探测地址写错了。判断办法是打开客户端的日志或开发者模式,看它实际请求的完整 URL 是什么。

第五步:Key 是活的但还是 401 或 403

余额为 0 的返回长什么样

有的网关在余额不足时返回 402 加 insufficient quota,有的直接返回 401 或 403。所以看到 401 先别急着换 Key,去后台看一眼余额和令牌状态,这一步 10 秒,能省掉半小时。

Key 被禁用、被删、超额的表现

后台把令牌停用之后,网关侧的返回通常还是 401。特征是你什么都没改,昨天能跑今天不能跑。这时候去后台看令牌列表,状态栏会写明白。

分组权限

平台上的令牌一般挂在某个分组上,分组决定它能调哪些模型。用低价分组去调高价模型,可能返回 403,也可能返回一个含糊的 401。换一个「全模型」分组的令牌复现一次,能通就是这个原因。

一个能一次排除掉这些的动作

拿后台里另一个确定在用的令牌,填进同一个客户端的同一个位置,跑一次。换了 Key 就好,说明是这把 Key 的权限或余额问题;换了还不行,说明是配置问题。

一段能长期用的自检代码

启动时先验 Key,别等业务请求

import os, requests

def check_key(base_url="https://xyuapi.top/v1", timeout=(10, 30)):
    key = os.environ.get("OPENAI_API_KEY", "")
    if not key:
        return "缺少环境变量 OPENAI_API_KEY"
    if key != key.strip():
        return "Key 首尾有空白字符,请去掉"
    if key.startswith("Bearer "):
        return "Key 里已经带了 Bearer 前缀,代码里不要再拼一次"
    try:
        r = requests.get(f"{base_url}/models",
                         headers={"Authorization": f"Bearer {key}"},
                         timeout=timeout)
    except requests.exceptions.Timeout:
        return "连接超时:先查网络,不是 Key 的问题"
    except requests.exceptions.ConnectionError as e:
        return f"连不上:{e}"
    if r.status_code == 200:
        return "Key 正常"
    if r.status_code == 401:
        return f"401 认证失败:{r.text[:200]}"
    return f"意外状态码 {r.status_code}:{r.text[:200]}"

print(check_key())

让它把三类失败分开报

这段代码的价值不在「能跑通」,而在把缺 Key、Key 脏了、网络不通、认证失败这四种情况报成四句不同的话。以后再出问题,看一行日志就知道该查哪一层,不用重新走一遍今天的流程。放在服务启动的第一行调用它,比出事之后翻半天日志划算得多。

速查表:症状直接对动作

你看到的原文直接动作
You didn't provide an API key请求头没发出去:检查客户端 Key 栏是否填了、代码里 header 是否真的加上
Incorrect API key provided: sk-abc***xyz对比它打印的字符和你配置里的值,先排除读错配置文件
invalid_api_key令牌已禁用去后台看令牌状态和余额
/v1/models 返回 200,业务请求 401Key 没问题,查业务请求的 header 拼装和中间层
手打 curl 通,客户端不通查客户端 base_url 是否精确到 /v1
昨天能跑今天不能跑,代码没改余额、令牌状态、分组权限三处一起看

在小鱼API 上怎么接才不容易踩 401

入口就填这两个

主入口 https://xyuapi.top/v1,备用 https://xyuai.cc/v1。两个都是 OpenAI 兼容协议,直接用 /v1/chat/completions/v1/models。小鱼API 是 AI API 接入平台,Key 在后台自助生成,随时可以新建和停用,建议测试用一个、正式用一个,出问题好定位。

支持哪些客户端

Chatbox、Cherry Studio、NextChat、Cline、RooCode、KiloCode、LobeChat,以及任何支持自定义 OpenAI 地址的客户端,填法都是同一套:地址填到 /v1,Key 填进 API Key 栏,模型名从 /v1/models 里挑。

成本上顺带说一句

按次计费为主,一次请求一个固定价,输入长度不影响价格,所以长提示词不会让单价变高。常用的几个:gemini-2.5-pro 0.031 元/次,deepseek-v4-flash-thinking 0.05 元/次,claude-sonnet-4-5-thinking 0.09 元/次,gpt-5.5 0.2 元/次。最低充值 7 元,支付宝微信都能付,不需要海外信用卡。

最后收一句

401 这个错最麻烦的地方是它太笼统,网络没问题、模型没问题,它照样报。所以别猜,跑命令/v1/models 验 Key,cat -A 验脏字符,手打 curl 验 header。三条命令跑完,答案一定在里面。

相关阅读

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

查看全部产品

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