iG iGetoken

大模型 API 报错排查手册:401 / 429 / 402 分别该改什么

更新于 2026-09-15 问题排查进阶玩法

免费 API 最常见的挫败感,不是领不到 Key,而是拿到 Key 之后返回一行看不懂的报错。

更麻烦的是报错码在不同平台的含义并不统一:同样是 429,在 A 家等 10 秒就好,在 B 家等一天也没用;同样是「额度没了」,有的返回 402、有的返回 403、有的直接给你 400。

这篇手册按「下一步该干什么」组织,而不是按状态码罗列。全文提到具体平台的额度与限速时,一律以资源库对应平台页为准(每页都标了核实日期)。

先用 30 秒定位

你看到的大概率是什么跳到哪一节
401 / 403鉴权方式或权限问题第二节(改配置)
404base_url 或模型 ID 写错第三节(改请求)
400 / 413 / 422参数错误或上下文超限第三节(改请求)
402 / 部分 403额度或余额没了第四节(查账号)
429撞速率限制第五节(该等)
超时 / 流式断在半路网络或服务端第五节(该等)

一个贯穿全文的前提:报错分两种性质——「你写错了」和「平台给不了」。前者改代码立刻见效,后者再改代码也没用。所以排查的第一步不是读报错,而是先确定这条请求在别的平台能不能跑通:能跑通 → 大概率是你在这家的配置问题;跑不通 → 大概率是请求本身有问题。

一、改配置:401 与 403

401 几乎只有一个意思:服务端不认你的身份。免费平台里它有四个高频成因。

① Key 没生效。 新建的 Key 有时需要几秒到几分钟才生效,粘贴后立刻调用会失败。等一分钟再试,「代码没动却好了」基本都是这个原因。

② 鉴权头名字不是 Authorization 这是最容易被忽略的一类——OpenAI 兼容 ≠ 只认 Bearer。有平台把认证头定义成 api-key,此时用标准写法必然 401,而报错信息不会告诉你该换成什么。用 OpenAI SDK 时得显式指定:

from openai import OpenAI

client = OpenAI(
    api_key="你的Key",
    base_url="https://example.com/v1",
    default_headers={"api-key": "你的Key"},  # 少数平台的认证头不是 Authorization
)

遇到「Key 明明是对的却 401」,第一件事是去平台页翻认证方式那一栏,而不是反复重新生成 Key。

③ 复制时带了空格或换行。 从网页复制 Key 常把换行一起带上,api_key.strip() 一下能解决相当一部分「玄学 401」。

④ 环境变量没刷新。setx 或写进 ~/.zshrc 之后,当前已开的终端不会自动生效,新开一个窗口再试。

403 比 401 更「业务」一些:常见于 Key 权限不足(比如只授权了部分模型)、账号未完成实名或未开通对应模型服务、以及请求来源被限制。403 先读响应体,它通常比 401 说得更具体。

二、改请求:404 / 400 / 413

404:八成是 base_url 的锅

模型 ID 写错一般返回 404 或 400,但绝大多数 404 是 base_url 写错了。三个高发点:

  • 少了 /v1,或反过来多写了 /v1(有的平台 base 里已经含版本号)。
  • 路径拼重了:SDK 会自动在 base 后面拼 /chat/completions,如果你把完整接口地址粘进 base_url,就会变成 .../chat/completions/chat/completions
  • 接口换版本了:平台升级 API 后旧地址会失效。有平台明确下线过 V1 接口,旧 base_url 调用直接失败、必须迁到新版地址——这类失效的特点是”以前能跑、现在不能跑”,且报错与你的代码无关

模型 ID 的高发点则是大小写与斜杠:同一个模型在 A 家叫 glm-4-flash,在 B 家可能叫 zhipu/glm-4-flash;开源系模型常带组织前缀(形如 Qwen/Qwen3-xxx)。别凭记忆写模型名,从平台页复制

400 / 413 / 422:参数与上下文

这一类的定位方法是「看服务端回给你的字段名」,它通常会明确指出哪个参数不合法。

症状真实原因处理
413 或「too many tokens」输入超过模型上下文裁历史消息或换长上下文模型
400 提到 max_tokens输出上限设太大调小,见下方说明
400 提到 messages 格式角色名或结构不合规检查是否误用了系统级字段
400/402 提到额度其实不是参数问题转到第四节

一个很容易踩的坑:上下文长度 ≠ 单次输出上限。 有的模型上下文 256K,但单次输出被限制在 64K 以内;你按上下文长度去设 max_tokens,就会稳定吃 400。另有平台的上下文是 1M、输出 128K。这两个数字要分开查。

还有几个平台特有的口径差异值得记住:

  • 有的平台按「Prompt 次数」计费而不是 Token:单次对话算 1 次,长上下文不会多扣,但 Agent 多轮和批量调用消耗飞快。
  • 超限返回的码不一定是 429:有的平台免费令牌用满后返回的是 400,如果你只按 429 做重试逻辑,就会一直重试一个永远不会成功的请求。

流式输出相关的 400

stream=True 之后报 400,常见于:SDK 版本过旧不认流式参数、把非流式响应当流解析、以及代理层不支持 text/event-stream先用非流式跑通,再开流式——这样能把「请求本身错」和「流式链路错」分开。

三、查账号:额度为什么「突然没了」

这类问题的迷惑性在于:代码一个字没改,昨天还好好的,今天全红。免费额度消失基本逃不出下面五种。

① 额度是「按模型独立」的。 你在 A 模型上还有额度,不等于 B 模型能用。有平台明确写了免费额度按模型独立发放、不能跨模型合并使用。切了个新模型就报错,先看额度是不是跟着模型走的。

② 额度带有效期,且各平台差异巨大。 有 30 天的、90 天的、2 周的、还有 5 小时窗口滚动恢复的。“送了多少”远不如”什么时候到期”重要——建议给每个平台记一个到期日(活动快讯里的截止日期可以直接拿来用)。

③ 重置时间不是你的「今天」。 有平台按 UTC 零点重置,与北京时间差 8 小时;有平台在北京时间凌晨 2 点重置;还有按 5 小时/7 天窗口滚动的。你以为是”今天没额度了”,其实只是跨过了它的重置边界。遇到「睡一觉就好了」的报错,基本属于这一类。

④ 额度用尽后服务被自动暂停,而不是降级。 这一点必须区分:

行为后果应对
降级请求变慢或换小模型,仍能用可接受
直接报错请求失败必须做容错
服务暂停需要手动去控制台开启必须监控 + 提醒

有平台的免费额度用尽且未关闭「安心模式」时,对应模型服务会自动暂停——不是报错,是彻底不响应,直到你手动处理。这类平台绝不能挂主力业务。

⑤ 网页版和 API 是两套体系。 这是新手最常误判的一条:某平台网页版聊天免费,但API 额度完全独立,Key 不通用、额度不共享。看到「网页能聊」就以为 API 也能用,必然踩坑。

判断额度问题的通用顺序:① 换一个模型名试试(排除按模型发放)→ ② 去控制台看真实剩余量而不是看报错 → ③ 核对重置时区 → ④ 确认是否需要手动开启服务 → ⑤ 最后才怀疑 Key 失效。

四、该等:429 与超时

429 是免费用户的老朋友。但它至少对应三种完全不同的限制,处理方式完全不同:

类型触发恢复方式
RPM / TPM 速率限制请求太密集 / 单请求太大秒级到分钟级,退避即可
日请求上限(RPD)一天调用次数用满等重置(注意时区)
临时限流高峰负载等几分钟,或换平台

判断技巧:看响应头。 不少平台会返回 Retry-Afterx-ratelimit-* 系列头,直接告诉你该等多久。有 Retry-After 就听它的,别自己拍脑袋。

判断”该等还是该换”:如果错误在几秒内重复出现且带 Retry-After,是速率限制,退避有效;如果退了 3 次还在报,多半是当天配额用尽,继续重试只是浪费你的时间——该切备用平台了。

速率限制的量级差异也同样巨大:宽松的按千次/天计,严格的只有个位数 RPM。同一个脚本在 A 家跑得好、在 B 家一跑就炸,八成是把自己写成了并发压测

可运行的退避重试

把「无脑重试」换成「按错误类型分流」,是这段代码唯一的价值所在:

import random
import time
from openai import OpenAI, APIStatusError, APIConnectionError, APITimeoutError

client = OpenAI(api_key="你的Key", base_url="https://example.com/v1")

# 可以重试的:限流、服务端错误、网络问题
RETRYABLE = {429, 500, 502, 503, 504}
# 不该重试的:改了也没用,重试只是浪费配额
FATAL = {400, 401, 403, 404, 413, 422}


def ask(prompt, model, max_retries=4):
    for attempt in range(max_retries):
        try:
            resp = client.chat.completions.create(
                model=model,
                messages=[{"role": "user", "content": prompt}],
            )
            return resp.choices[0].message.content

        except APIStatusError as e:
            if e.status_code in FATAL:
                raise                      # 参数/鉴权错,重试无意义
            if e.status_code not in RETRYABLE:
                raise

            # 服务端给了 Retry-After 就听它的
            wait = None
            if e.response is not None:
                ra = e.response.headers.get("retry-after")
                if ra and ra.isdigit():
                    wait = float(ra)
            # 否则指数退避 + 抖动(避免多进程同时重试、再次撞在一起)
            if wait is None:
                wait = min(2 ** attempt, 30) + random.uniform(0, 1)

            if attempt == max_retries - 1:
                raise                      # 交给上层做切换
            time.sleep(wait)

        except (APIConnectionError, APITimeoutError):
            if attempt == max_retries - 1:
                raise
            time.sleep(min(2 ** attempt, 30) + random.uniform(0, 1))


print(ask("用一句话介绍你自己", "某免费模型"))

两个细节值得单独说:

  • 抖动(jitter)不是可选项。 指数退避如果不加随机量,多个进程会同时醒来、同时重试,把限流打成持续限流。
  • FATAL 列表是这段代码的核心。 区分「重试有用」和「重试有害」,比退避算法本身重要得多——反复重试一个 401,只会让你在平台的监控里更难看。

超时与流式中断

超时几乎永远是三个原因:请求太大、网络到该平台不稳、以及平台本身在高峰。处理顺序是 先给客户端显式设 timeout(默认值通常过长或过短)→ 再降 max_tokens → 最后换平台

流式中断要分两种看:断在开头是连接没建起来,重试即可;断在中途则已经消耗了 Token 却拿不到完整结果,这时应该把已收到的内容存下来、缩小输入重试,而不是原样重来。

五、三个常见误判

误判一:把「平台限流」当成「我的代码有 bug」。 特征是时间不规律、报错时有时无、换个时间点就好了。

误判二:把「免费额度取消」当成「Key 失效」。 平台调整政策是很常见的运营动作,有平台明确取消过原先的免卡额度、也有关闭过免费档。看到全网都在报同一个错,先去平台页和活动快讯看一眼口径变化。

误判三:认为「报错信息里写的就是真相」。 前面说过,额度耗尽可能伪装成 400。永远以控制台的真实数据为准

六、排查完之后:让它自动处理

手动排查只能解决问题一次,代码里的分流逻辑才能解决问题一辈子。这正好是下一篇的内容:

本文只讲方法与口径,不保证任何具体额度长期有效——文中提到的平台请以 资源库平台页 为准,每页都标注了核实日期。

还没开始?从 《领取第一个免费 Key》 读起。担心踩坑,先看 避坑指南