接口一调就红,返回 400、401 还是 403,很多人第一反应是「Key 坏了」,改半天环境变量还是错。这三个码说的其实是三件不同的事:请求本身就是坏的、你没证明自己是谁、你证明了但没权限。这篇按「先看谁在拒绝你、再看拒绝的理由」的顺序,给出判别步骤和每类码的具体改法,让你别再瞎试。
接口一调就红,返回 400、401 还是 403,很多人的第一反应是「Key 坏了」,然后开始换环境变量、重启服务、换账号,折腾半天还是错。其实这三个码说的是三件完全不同的事:请求本身就是坏的、你没证明自己是谁、你证明了但人家不让你做这件事。分不清这三件事,排查就是瞎试。
这篇按一个固定顺序讲:先看谁在拒绝你,再按状态码对号入座,最后每次只改一个变量重试。你照着走一遍,基本能把问题范围缩到一两处。如果你是在用中转站调模型,排查逻辑一样,只是「谁在拒绝」多了一层,可以配合到 /sites 看对应渠道的检测记录一起判断。
先搞清楚是谁在拒绝你
同一个 400,可能是你自己的代码写错了,也可能是中间网关没认出来你的请求格式,还可能是上游模型服务真的不认这个字段。这三种情况下修法完全不同,所以排查的第一步不是看码,而是看「谁发的这个码」。
- 客户端代码:你本地拼的 body、header 有问题,报错信息通常带具体字段名
- 你的网关或中转层:错误结构偏网关风格,常有 request id、upstream 字段
- 上游模型服务:错误结构和官方文档里的示例一致,常带 error.type / error.code 这类嵌套字段
- 判别方法:换一条直连方式(或换个渠道)用同样的请求再打一次,看错误结构变不变
- 把「谁拒绝的」写进日志当第一字段,后面所有排查都依赖它
400:请求本身就不合法,服务端根本没打算处理
400 的意思是「我看不懂你要干什么」,服务端在解析阶段就把你拦下来了,压根没走到鉴权那一步。所以这时候你应该盯的是请求内容的格式,而不是 Key。
- JSON 语法错
- 多一个逗号、少一个引号、中文引号混进去都会触发
- 字段类型不符
- 要求字符串你给了数组,要求数字你给了字符串
- 必填缺失
- 少了 model、messages 这类必要字段
- 多包一层
- 把整个 body 又套进一个 data 字段里发出去
- Content-Type 不对
- 声明成表单或纯文本,服务端按错格式解析
- 编码问题
- 非 UTF-8 或带了 BOM 头
- 先做语法自检把要发的 body 原样丢给本地 JSON 解析器,能解析通过再谈其他;这一步能挡掉相当一部分 400。
- 发最小可用请求只保留必填字段,能通就说明是某个可选字段惹的祸。
- 逐字段加回来每次只加一个字段重试,加到哪个报错,问题就在哪个字段。
- 对照官方文档口径字段名大小写、枚举值写法、协议方言(比如是否包 extra_body)都可能和你想的不一样,以文档为准。
什么情况下不适用:如果请求体已经确认合法,仍然稳定报 400,那就要怀疑是中间层对协议做了改写,或者上游对某些字段采用了不同的命名口径。这时候换个渠道对照一下最省事,渠道之间的差异可以到 /rankings 看当时的对比情况。
401:没证明你是谁,或者证明过期了
401 是身份问题。请求格式可能完全没问题,但服务端说你没带凭证、或者带的凭证它不认、或者凭证已经失效。它和 400 的分界很清楚:400 是「话没说明白」,401 是「你是谁」。
- Header 里根本没带鉴权字段,比如漏了 Authorization
- Key 拼写错、前后多了空格、换行符混进去了
- 前缀写错,比如该写 Bearer 你写了别的,或者多写了一层
- Key 已经被撤销、重置,或者本身有有效期,到期就失效
- 把 Key 放在了 query 参数里,而服务端只读 Header
有一个特别省事的分界实验:把鉴权 Header 整个摘掉,重发一次。如果返回从 400 变成了 401,说明你的请求格式本身没问题,问题就落在凭证这一条线上;如果摘掉之后还是 400,那说明格式那关就过不去,先回去修 400。
- 打印实际发出的请求头注意是「实际发出」,不是你以为发出的。脱敏后打印,确认落在线上的是那一份。
- 核对前缀和拼写逐字符比对 Header 名和值,空格、大小写、换行都要看。
- 确认 Key 状态去对应平台确认这个 Key 是否被撤销、是否过期、是否有调用范围限制。
- 换一个新 Key 试如果新 Key 能通,说明是旧 Key 的问题;如果不能通,回到上一步继续查组装逻辑。
403:身份认出来了,但不让你做这件事
403 是最容易被误判成 401 的一个。它的意思是:我知道你是谁了,但你没资格做这件事。所以换 Key 通常没用,你得改权限、改来源,或者换一条能覆盖这个能力的渠道。
| 可能原因 | 怎么确认 | 先做什么 |
|---|---|---|
| Key 没开通这个模型 | 同一 Key 换个模型打一次 | 在平台里查这个 Key 的模型授权范围 |
| 额度或套餐不含该能力 | 看账户的能力清单 | 确认套餐覆盖范围,或换渠道 |
| IP / 地区不在允许范围 | 换网络环境对照一次 | 核对服务商的可用区域口径 |
| 被风控标记 | 多个调用方同时开始报错 | 联系服务方确认,别改客户端 |
| 请求的资源不属于该账号 | 换一个同类型资源打一次 | 核对资源归属和授权关系 |
和 401 的关键分界:401 换对 Key 就好,403 换 Key 通常没用。如果你发现自己换了三四个 Key 都是 403,那就别再换了,问题不在凭证上。
什么情况下别急着改代码:如果同一个 Key 换个模型能通、换回原模型就不通,基本可以判定是权限或配额口径的问题,你的请求写得没错。这时候该做的是去核对授权范围,而不是回头改请求体。渠道侧的能力覆盖情况,可以到 /price-watch 看当时的追踪记录。
三步判别顺序:先分层、再对码、后改一处
- 第一步:固定证据完整请求(脱敏)、完整响应体、响应头、时间戳,一次全抓齐再动手。缺证据就开始改代码,只会越改越乱。
- 第二步:按三层定位顺序是「谁拒绝 → 状态码 → 响应体里的原因字段」。先判断是客户端、中间层还是上游,再按 400/401/403 对号入座。
- 第三步:只改一个变量再重试改多了即使成功了,你也不知道是哪一步起的作用,下次遇到同样的问题还是不会。
排查的本质不是「试到能通」,而是「每次都能缩小范围」。三个码就是三把不同方向的缩小范围的刀。
把排查变成可复现:日志要记哪几个字段
一次线上失败如果没法在本地复现,排查就只能靠猜。要把排查变成可复现的,关键是失败那一刻把该记的都记下来,而不是事后回忆。
| 要记的字段 | 为什么 | 注意 |
|---|---|---|
| 状态码 + 原因字段 | 定类的基础 | 响应体里的 error 结构最好原样保留 |
| 请求 ID | 跨层追查用 | 有就记,没有就算了 |
| 目标地址 + 模型名 | 区分是哪条链路出错 | 中转场景尤其重要 |
| 耗时 + 重试次数 | 区分是拒绝还是超时 | 超时和 4xx 是完全不同的线 |
| 失败阶段 | 连接 / 发送 / 读取 | 能直接区分网络问题和内容问题 |
- 不要记:完整 Key、完整用户输入内容、任何可以直接拿去用的凭证
- 脱敏要落在写日志的那一行,而不是事后过滤,事后过滤一定会漏
- 日志字段固定下来,团队里所有人排查时看到的东西才是一样的
有这组字段,一次线上失败可以直接在本地照着复现,不用靠猜,也不用反复问「你那边是怎么调的」。
什么时候别在客户端死磕
不是所有失败都该你来修。有些情况下问题确实不在你的代码里,继续改客户端只是浪费时间。判断标准有这么几条。
- 同一份请求换个网络环境或换条线路就过,问题在链路或出口,不在代码
- 多个不同调用方同时开始报同一个码,先去看服务状态和公告
- 响应体是网关风格的错误结构,而不是模型服务风格,说明你面对的是中间层
- 换了三四个不同的 Key,错误码和错误结构完全一致
- 本地跑得好好的,一上生产就错,优先怀疑配置加载和网络出口
一张对照表:症状 → 大概率的码 → 先做的动作
| 你看到的现象 | 大概率的码 | 先做的动作 |
|---|---|---|
| 本地能跑、上线就错 | 401 | 查环境变量和 Header 组装,确认线上加载的是哪份配置 |
| 格式校验类报错、字段验证失败 | 400 | 发最小可用请求,再逐字段加回来 |
| 同一 Key 部分模型可用、部分不可用 | 403 | 查这个 Key 的模型授权范围,别换 Key |
| 拿不准属于哪一类 | 先做摘 Key 实验 | 把鉴权 Header 摘掉重发,看返回怎么变 |
| 超时或连接被重置 | 不是 4xx | 去查网络链路,别在状态码上浪费时间 |
拿不准属于哪类时,摘 Key 实验是最省事的分界手段,一次就能把 400 和 401 分开。如果分完还是不确定,就把完整请求和响应都留着,拿去问渠道方或者对照站内的检测记录。
下一步如果你是在用中转站调接口,光看状态码还不够,得知道这条渠道本身是否稳定、是否被多人反馈过同类错误。到站内中转站目录里,按渠道看检测记录和近期反馈,比你自己一个个试要快得多。→常见问题
400、401、403 里,哪个跟 Key 没关系?
400 基本跟 Key 无关,它说的是请求本身不合法,比如 JSON 语法、字段类型、必填缺失。401 才是纯粹的凭证问题,403 是凭证有效但权限不够。排查时先按这个分工定位,能省掉大量无效的换 Key 操作。
为什么我把 Key 换对了还是 403?
因为 403 不是身份问题而是权限问题。常见原因是这个 Key 没开通你调的模型、套餐范围不含该能力、来源 IP 不在白名单,或者被风控拦下。换 Key 通常无效,要看渠道的权限说明和你的调用来源。
同一个 Key 昨天能用今天报 401,是什么变了?
优先查三处:Key 是否被撤销或轮换、环境变量是否被覆盖、请求头组装里有没有多出空格或换行。其次看凭证是否有有效期。逐项对照实际发出的请求头,比反复重启服务有用得多。
响应体里给了原因字段,还要不要看状态码?
要。原因字段是补充说明,状态码决定你该去哪一类里找答案。两者不一致时以状态码定大类、以原因字段定细节。有些中间层会把上游错误包装成自己的结构,这时状态码反而是更可靠的那条线索。
排查这类问题,日志最少要记哪几项?
状态码、目标地址、模型名、耗时、失败阶段、重试次数这几项就够复现。Key 只记前缀或指纹,不要记全量。有这组字段,一次线上失败能在本地原样重放,不用靠加打印反复上线试。