DeepSeek API 报错全解:七个状态码的含义与修复

凌晨一点。key 贴好了,base URL 配好了,手指敲下回车跑第一条 curl——0.4 秒后,终端吐出一个 400 状态码和一坨 JSON,正文精确指出请求断在哪里。十五个词的解释,每个字都是真的,加起来什么用都没有。这一幕我不止一次亲历,而且总发生在不该写代码的钟点。

DeepSeek API 的每条报错都长这样:技术上完整,实操上没用——直到你学会读它。这篇文章把每个状态码从日志里的红字变成具体的修复动作:地图用官方错误码表,路况用社区真实案例。如果你还在配置阶段,先看 DeepSeek API 上手指南;这篇是给"昨天还能跑、今天突然不行"的人准备的。

DeepSeek 官方只有七个状态码

官方文档列出的错误码恰好七个,不多不少。这是好消息:你的报错必然落在这七个桶里,而且每个桶都有明确的责任人——你、你的账户、或者服务端。

名称含义谁来修
400Invalid Format请求体格式错误
401Authentication FailsAPI key 不对
402Insufficient Balance账户余额不足
422Invalid Parameters格式对,但参数值非法
429Rate Limit Reached触发并发限制你,然后等
500Server Error服务端内部错误DeepSeek
503Server Overloaded服务过载DeepSeek,然后等

这张表还能再分三组。改请求:400 和 422——服务端拒收你发的东西,原样重发一万次也一样失败。改账户:401 和 402——问题在 key 或余额,不在代码。等一等再重试:429、500、503——请求本身没问题,是时机不对。

注意表里没有的:504。DeepSeek 不发 504。如果你看到 504,那是你和模型之间的某个中间层——网关、代理、云函数——自己造出来的。后面细说。

把请求想象成一封信。400 是信封格式写错,柜台直接退回,根本进不了分拣机;401 是窗口职员不认你的证件;402 是邮票用完了。这三种情况下,把同一封信再寄一次不会有任何变化——要改的是信封、证件或邮票本。

400:请求坏掉的六种方式

最常见的错误码,花样也最多。一份中文社区的生产环境跟踪把 400 归因大致分成了参数问题约 42%、数据格式约 28%——和英文论坛的分布对得上。下面六种模式,几乎覆盖你会遇到的一切;其中第一种,我自己隔几个月就会重新踩一遍。

1. messages 传了对象而不是数组。 API 要的是列表,单独一条消息包在字典里不行。这份会被拒:

{"model": "deepseek-v4-flash", "messages": {"role": "user", "content": "Hello"}}

这份能通过:

{"model": "deepseek-v4-flash", "messages": [{"role": "user", "content": "Hello"}]}

2. 数字被传成了字符串。 "temperature": "0.7" 失败,"temperature": 0.7 通过。你的 JSON 序列化器可能在背后偷偷加引号——检查真正发出去的报文,而不是源代码里写的那份。

3. 用了已弃用的模型名。 这一条最阴险。旧名 deepseek-chatdeepseek-reasoner 已于 2026 年 7 月 24 日退役,现行模型是 deepseek-v4-flashdeepseek-v4-pro。成千上万的教程还挂着旧名,从 2025 年的博客原样抄来的代码今天必然报错。现行模型清单见我们的 V4 模型解读

4. tools 数组格式错误。 工具定义的 schema 跑偏——嵌套层级不对、缺 type 字段——在推理开始前就会被拒。LibreChat 这类前端用户报过一模一样的案例。

5. image_url 内容块。 文本 API 不接受消息里带图片附件;把视觉格式的载荷发给 deepseek-v4-flash,返回的是 400,不是优雅降级。

6. 客户端协议不匹配。 最诡异的一种:Claude Code 用户接 DeepSeek 时撞上 400 Failed to deserialize the JSON body... messages[1].role: unknown variant 'system', expected 'user' or 'assistant'。部分 Claude Code 版本(2.1.154 及以后)不接受那个位置的 system 角色,而 DeepSeek 的 OpenAI 兼容端点恰恰会发——一篇有完整记录的案例给出的修复:降级到 @anthropic-ai/claude-code@2.1.148 并设 DISABLE_AUTOUPDATER=1。两边都没 bug,只是两套协议各自往前走,走岔了。当前兼容状态我们跟踪在 Claude Code 接入指南里。

400 和 422 怎么分

分界线是格式与数值。400 说你的 JSON 根本没法解析成一个合法请求——结构坏了、类型错了。422 说结构没问题,但里面的某个值不合法,比如 max_length 超过模型的上下文窗口。排查动作也不同:400 去看原始请求体;422 去读参数文档、核对你的数字。

401:key 的四种死法

服务端的意思很直白:不认你这个人。四种原因覆盖几乎所有情况:

  1. 用错了别家的 key。 OpenAI(或任何其他厂商)的 key 指向了 DeepSeek 的 base URL。长得都像 sk-...,能用的只有一个。
  2. 环境变量根本没加载。 .env 文件好好躺在那,但进程没读过它——客户端拿着空字符串就出门了。
  3. 不可见字符。 复制 key 时带上的尾部换行或空格。打印 repr(api_key) 亲眼看看。
  4. 代理剥掉了 Authorization 头。 企业代理和某些 API 网关会悄悄干这事。

六十秒诊断法:用 curl 直接打一个便宜的带鉴权端点(比如列出模型),中间不放任何东西。如果成功,key 是活的,嫌疑落在你的应用层——完整排查清单见 API key 指南

402 余额不足:唯一永不重试的错误

没有歧义:账户没钱了。充值之前,代码一行都不会跑。

诱惑在于把 402 也塞进统一的重试循环。别。重试余额不足只会刷屏日志,刷不出 token——这个状态在两次尝试之间不可能自愈。真正有用的是监控:余额掉到一天的消耗量以下就告警,按节奏充值。你的负载一天烧多少钱,计费拆解里有答案。

429:并发按账户算,不是按 key 算

这个机制大多数人理解错了。DeepSeek 度量的是并发连接数,不是每分钟请求数——而且按账户计,不按 key 计。你生成五个 key,共享的还是同一个池子,就像五张借记卡取的是同一个账户的钱。按照速率限制文档,截至 2026 年 8 月,上限是 deepseek-v4-pro 500 并发、flash 系模型 2500 并发。

一个"请求"从发出的那一刻起占用一个槽位,直到响应完全返回——流式调用要等到流关闭。一次长思考会全程占着槽位,这正是批量任务同时开火会撞上限、同样的量摊开几分钟就没事的原因。

三条出路,按省事程度排:

  • 摊开负载。 批量任务排队、worker 错峰,并确认你自己的重试逻辑没有制造风暴(一批失败后全部同时重试,会在最糟的时刻把占用翻倍)。
  • 给请求打 user_id 标。 如果你用一个账户服务很多终端用户,传 user_id 能让每个用户拿到独立的并发额度——多租户的官方逃生门。标识符必须匹配 [a-zA-Z0-9_-]、不超过 512 字符、不含隐私信息。OpenAI 风格的 SDK 用 extra_body={"user_id": ...} 传;Anthropic 的 SDK 用 metadata={"user_id": ...}
  • 申请扩容。 容量扩展免费,同一个文档页上有申请入口。

还有一个让团队意外的 429 来源——我曾在一个"放着保险"的批处理任务上亲历过:重试风暴。一波请求失败后,所有 worker 按同一个定时器重试,整齐划一的第二波往往触发了第一波都没触到的限制。解药是抖动(jitter)——给重试延迟加随机量。

500 和 503:轮到服务端背锅

500 是服务端处理一个合法请求时自己出了错;503 是过载拒单。两者都是暂时的,都是 DeepSeek 该修的,都值得退避重试。

一个细节:不是所有服务端的挣扎都表现成状态码。一个 200 响应的报文体里可能带着 finish_reason: "insufficient_system_resource"——模型开跑了,中途资源不够,提前停了。这不是 503,也不能盲目重试;先检查报文里的 finish_reason 字段,再下结论。

超时和 504:DeepSeek 从不发出的错误

翻回上面的七码表。没有 504——因为 504 Gateway Timeout 是你代码和 DeepSeek 之间的某个东西生产的:你的 nginx、你的云函数平台、公司代理。网关按它配置的耐性等了(通常 30 或 60 秒),没等到完整响应,就宣布上游死了。OpenClaw 用户对这深有体会——它约 60 秒的内部超时掐断的恰恰是那些什么问题都没有的长生成。

修法几乎永远是调高这一跳的超时——nginx 的 proxy_read_timeout、serverless 的函数超时——而不是动 DeepSeek 的代码。

还有两个时间相关的机制值得知道,都来自速率限制文档:

keep-alive 信号。 长等待期间——比如深度推理调用出第一个 token 之前——服务端会发保活字节,防止中间层把沉默当成断线。非流式响应周期性发空行;流式响应发 SSE 注释行(: keep-alive)。如果你的客户端库或代理丢掉这些信号,可能自己判定连接空闲然后杀掉它。处理好它们,至少别让它们撑爆你的解析器。

10 分钟死线。 请求排队超过十分钟还没开始推理,服务端直接关闭连接。高峰时段这就是队列替你放弃。此时带退避的重试是对的——如果持续发生,先看 DeepSeek 的状态公告,再怀疑自己的代码回退了。

还有一个比想象中常见的奇案:有中文社区文章记录过区域性 504,根因是 DNS 污染——某些网络里 API 域名解析出错,直连 IP 却正常,换成公共 DNS 后解决。如果 504 按地理聚集、其他都排查过了,先换个 DNS 试试,再改客户端。

客户端超时也值一句话:连接超时设在 3 秒左右,读取超时要容得下你最长的生成——对话 30 秒是地板,深度推理远不是天花板。

一张可以直接贴在客户端旁边的重试决策表

整套 DeepSeek 报错解决思路压缩成一张表:

重试?第一动作
400永不修请求体;把原始载荷打进日志
401永不curl 验 key;查环境变量加载
402永不充值;加余额告警
422永不对照文档核对参数值
429退避+抖动;考虑 user_id 隔离
500指数退避
503指数退避;看状态公告
超时 / 504先调网关超时,再重试

重试逻辑本身小到可以背下来——只重试 {429, 500, 503},每次等更久,再加随机:

RETRYABLE = {429, 500, 503}

def call_with_backoff(client, payload, max_attempts=5):
    for attempt in range(max_attempts):
        try:
            return client.post("/chat/completions", json=payload)
        except HTTPError as e:
            if e.status not in RETRYABLE:
                raise  # 你的 bug —— 修它,别重试它
            delay = min(8, 0.5 * 2 ** attempt) + random.uniform(0, 0.5)
            time.sleep(delay)  # 基数 0.5 秒,封顶 8 秒,外加抖动
    raise RuntimeError("退避后仍然失败")

社区的一篇排查手册在这之上补了熔断模式:连续失败 N 次后停手冷却,先发一个探针请求再恢复全线。跑生产流量时值得加上;写脚本就是杀鸡用牛刀。

常见问题

DeepSeek API 400 最常见的原因是什么?

messages 字段格式坏——通常是该传数组传了对象、该传数字传了字符串,或者从挂着 2026 年 7 月已退役模型名的旧教程里抄来的请求。把实际发出的请求体打进日志(不是你以为发的那份),一眼就能看到凶手。

为什么请求一直转圈,最后什么都没返回就断了?

两个嫌疑。如果在任何输出之前就断,多半是 keep-alive 机制被剥掉了:服务端发空行(或 SSE 注释)防止中间层杀连接,而链路上某个环节把它们丢了。如果整整十分钟后才断,那是推理启动死线——队列替你放弃了。两种都值得退避重试;反复出现就值得审计你到 API 之间每一层的超时设置。

504 是 DeepSeek 的错误吗?

不是。官方文档定义了七个状态码,里面没有 504。504 由你和服务之间的网关或代理在等待超时后生成。把那一跳的超时调高。

哪些错误该重试,哪些永远不该?

429、500、503 用指数退避加抖动重试。400、401、402、422 永不重试——它们是确定性失败(格式坏、key 坏、没余额、值非法),在你改变点什么之前,每次重试都返回同样的结果。

Claude Code 连 DeepSeek 为什么报 400?

部分 Claude Code 版本不接受消息数组里的 system 角色,而 DeepSeek 的 OpenAI 兼容端点恰好会发——反序列化当场失败。降级到 @anthropic-ai/claude-code@2.1.148 并关闭自动更新,是协议错位解决前社区验证过的修复。


状态码就是 API 在跟你说话——话少、字面、且永远诚实地说出问题在链路的哪一侧。七个数字,三个桶,一个问题:信封、证件,还是排队?答对这一问,日志还没打开,调试就做完了一半。