DeepSeek API 报 401 和 402 的区别:key 没错却调不通
「key 我复制了三遍,肯定是对的,怎么还是调不通。」这句话在接国产模型的开发者群里几乎每周都要出现一次。而它相当一部分的结局是:报错根本不是 401,而是 402——账号余额不足。两个码的成因完全不搭,但人的直觉是「调用失败就是凭证有问题」,于是时间全花在反复复制粘贴密钥上,真正要做的事其实只是打开余额页看一眼。
DeepSeek 官方文档里有一张错误码表,短得一屏就能看完,却是排这类问题最省事的东西。下面按「先分清是哪一类失败」的顺序走一遍:先把码读准,再决定查什么。
一、官方错误码全表
| 码 | 官方描述 | 官方给的解决方法 |
|---|---|---|
| 400 | 格式错误:请求体格式错误 | 按错误信息提示修改请求体 |
| 401 | 认证失败:API key 错误 | 检查 key 是否正确;没有 key 就先创建 |
| 402 | 余额不足:账号余额不足 | 确认账户余额并前往充值 |
| 422 | 参数错误:请求体参数错误 | 按错误信息提示修改相关参数 |
| 429 | 请求速率达到上限(TPM 或 RPM) | 合理规划请求速率 |
| 500 | 服务器故障:服务器内部故障 | 等待后重试;一直存在则联系官方 |
| 503 | 服务器繁忙:负载过高 | 稍后重试 |
这张表真正值得记的不是每一条的解决方法,而是它背后的责任划分:400 和 422 是你发出的请求本身不合法,401 是你的身份不被承认,402 是账号没钱,429 是你太快了,500 和 503 是对面的问题。五类的责任方各不相同,修法也毫无交集。
顺便说清 400 与 422 的分界,这两个也常被混着改:400 的官方描述是「请求体格式错误」,422 是「请求体参数错误」。前者通常是 JSON 本身坏了或者请求头不对,后者是 JSON 合法、但某个字段的值服务端不接受。官方对两者给的办法都是「按错误信息提示修改」——这句话的潜台词是响应体里带着具体提示,别只盯着状态码就上手猜,先把响应体打出来读完。
二、401:哪些情况真的是 key 的问题
官方对 401 的描述只有一句:API key 错误,认证失败;解决方法是检查 key 是否正确,如果没有就先创建。这句话很短,但「不正确」在实际工程里有好几种形态,值得一个个排掉:
- 复制时带进了空白字符。 从控制台复制、或者从聊天工具里粘贴,前后很容易跟着一个空格或换行。写进
.env的时候尤其常见,因为不少配置解析不会帮你把两端裁掉。 - 没有复制完整。 密钥通常只在创建那一刻完整显示一次,尾部漏掉几位,之后就再也对不上,而你从本地文件看它「长得挺像」。
- Authorization 头的格式不对。 官方 curl 样例里写的是
Authorization: Bearer ${DEEPSEEK_API_KEY}——Bearer后面跟一个空格再接密钥。少写Bearer、大小写拼错,或者在 shell 里用了单引号导致变量没被展开(实际发出去的是字面量${DEEPSEEK_API_KEY}),服务端看到的都是一个无效凭证。这一类的特征是:密钥本身没问题,你越核对密钥越查不出来。 - key 已经被删除或停用。 密钥在控制台里被删掉、或者因为泄漏被处理过之后,本地那串字符再正确也没用。这条没法在代码侧确认,只能回控制台的密钥列表核对。
- key 和 base_url 不是同一家的。 这是团队里最容易出、也最费时间的一种。DeepSeek 用的是与 OpenAI、Anthropic 兼容的接口格式,所以换供应商往往只改一个
base_url就跑起来了。于是常见的错位是:环境变量里还留着另一家的密钥,base_url已经指向https://api.deepseek.com;或者反过来,DeepSeek 的密钥被发到了别家的端点。凭证本身完全有效,但发给了不认识它的服务端,结果就是 401。
还有一个同源的细节:DeepSeek 的两种协议对应两个不同的 base_url——OpenAI 格式是 https://api.deepseek.com,Anthropic 格式是 https://api.deepseek.com/anthropic。这两者填错更多表现为路径或请求体不被接受,但排查顺序上是一样的:先确认「密钥、base_url、SDK」这三样属于同一套,再谈密钥本身对不对。密钥从申请到落地的完整流程可以对着这篇 DeepSeek Key 教程走一遍;401 这个码在各家平台上的通用排查思路,另有一篇专门讲 401 的文章。
三、402:这是余额的问题,不是 key 的问题
402 在官方表里的描述是「余额不足:账号余额不足」,解决方法是确认账户余额并去充值页充值。它跟密钥没有任何关系——密钥完全正确、代码一个字都不用改,照样会 402。
值得单独讲的是扣费机制,因为「我明明看到账户里有余额」这个困惑基本都出在这里。官方计价页的原文口径是:扣减的费用直接从充值余额或赠送余额中扣减;当两者同时存在时,优先扣减赠送余额。
两个余额池、而且先扣赠送的那个,意味着控制台上那个数字要分开来看:
- 只要还在扣赠送余额,充值余额就是不动的,看上去一直是满的;
- 一旦赠送那部分耗尽或失效,扣费才开始落到充值余额上——这个切换时刻不会有人提醒你。
这里必须把话说清楚:赠送余额到底有没有、有多少、有没有有效期,官方文档里没有说明。 所以本文不给任何金额和期限,一切以控制台余额页显示的为准,那一页是唯一的准。官方自己也在计价页写了「产品价格可能发生变动,DeepSeek 保留修改价格的权利」,所以定期回去看一眼本来就是应该的。
工程上从 402 里能拿走的经验是:余额是一个会归零的外部状态,不是一次性的配置。上生产之前就该把它当成依赖来看待——至少在应用层把 402 单独识别出来告警,而不要让它混在通用的「调用失败」里被重试逻辑无限重试。重试一个 402 永远不会成功,只会把日志刷满,顺带掩盖真正的原因。
四、429:给每个服务发一个独立 key,并不能隔离配额
官方对 429 的描述是「请求速率(TPM 或 RPM)达到上限」。这一条背后有个很多人不知道的机制,写在限速与隔离页上:
并发限制以账号粒度计,与 API Key 无关。
按官方说明,一个请求从发出后到模型响应完成之前记为一个并发;在并发限度内请求都会得到响应,超过限度就会收到 HTTP 429。
这句话直接推翻了一个相当普遍的直觉:「给每个服务、每套环境各发一个 API Key,这样它们的配额就互不影响」——这是错的。十个 key 和一个 key 在并发上是同一个池子。所以线上服务被内部某个批处理脚本挤到 429 的时候,换 key 是没有用的,要做的是在自己这一侧排队限流。
真正用来做细粒度管理的是 user_id 参数。按官方说明,它能做三件事:内容安全隔离、KVCache 隔离、调度隔离。用它有几条硬约束:
- 格式必须满足正则
[a-zA-Z0-9\-_]+,最大长度 512; - 官方明确要求不要在 user_id 里包含用户隐私信息,所以别图省事直接塞手机号或邮箱,用一个内部映射过的 ID;
- 传法按协议分:OpenAI 接口放在请求体的
user_id字段(用 OpenAI SDK 时要塞进extra_body),Anthropic 接口放在metadata.user_id。
但这里有个前提差别,不看清会白做:对普通 API 用户,所有 user_id 是合并计算并发限速的。 也就是说你老老实实标了 user_id,隔离到的是内容安全、缓存和调度,并发仍然是一个池子。只有在提升了并发配额之后,官方才会在限制账号总并发的同时,对每个传入的 user_id 分别限制(空 id 算一个特殊的 user_id),某个 user_id 超限时,只有带这个 id 的请求收到 429。所以想用 user_id 给某个大客户兜住并发,得先把配额提上去。
配额不够的正路是提工单:官方写明可以提交账号扩容申请工单,按实际业务需求匹配并发量,而且扩容不增加额外费用。这比在代码里堆重试划算得多。具体的并发数值本文一个都不写——那是会变的配额,以官方限速与隔离页为准。429 的通用退避与排队做法见这篇 429 排查,国产几家平台在限速口径上的差异见限速对比。
五、症状 → 先查什么
| 你看到的 | 大概是哪一类 | 第一步做什么 |
|---|---|---|
| 401 | 身份不被承认 | 核对密钥、base_url、Authorization 头是不是同一套;再去控制台确认这把 key 还在 |
| 402 | 账号没钱 | 打开余额页,分别看充值余额与赠送余额;别动代码 |
| 400 | 请求体格式不合法 | 把响应体里的提示读完;先确认 JSON 本身合法、请求头带对 |
| 422 | 某个字段的值不被接受 | 同样读响应体提示,定位到具体字段再改 |
| 429 | 速率或并发到顶 | 先看自己这边并发是不是放开了;记住换 key 无效,要限流或提工单扩容 |
| 500 | 服务端内部故障 | 退避重试;一直不好就联系官方 |
| 503 | 服务端负载过高 | 稍后重试,别加压重试 |
| 长时间没响应、最后连接被关掉 | 保活机制,不是认证问题 | 官方说明请求等待期间会持续返回空行(非流式)或 SSE keep-alive 注释(流式),自己解析 HTTP 响应时要处理掉这些内容;若长时间仍未开始推理,服务器会关闭连接 |
| 调通了但行为跟预期不一样 | 参数被静默忽略,或模型名过期 | 见下一节,这类根本不报错 |
这张表的用法只有一条:先读码,再动手。 401 和 402 的排查路径完全不交叉,把这两个分清,大半时间就省下来了。
六、还有一类问题根本不报错
「key 没错却调不通」还有一种更隐蔽的版本:请求返回 200,但结果不对,于是人开始怀疑凭证和网络。这一类在官方文档里是有依据的,只是不产生错误码。
一个是参数被静默忽略。官方写明思考模式默认是打开的,而在思考模式下不支持 temperature、presence_penalty、frequency_penalty——原话是为了兼容已有软件,设置这些参数不会报错,但也不会生效。top_p 的情况类似,在思考模式下有下限约束,非思考模式下该参数恒定、传入值被忽略。也就是说你调了温度却看不出任何变化,这不是你调得不够,而是它压根没进去。
另一个是模型名过期。官方更新日志公告过:旧有的两个模型名 deepseek-chat 与 deepseek-reasoner 已于 2026-07-24 停止使用;而当前文档里仍标明「可以调用」的旧名只有 deepseek-v4-flash 与 deepseek-v4-flash-vision-exp,它们会被路由到 V4.1 Flash 提供服务并按 Flash 价格计费。当前该用的模型名是 deepseek-flash 与 deepseek-v4-pro。用一个已经停用的名字会返回哪个码,官方文档里没有说明,本文也无从验证,所以这里不做断言;但排查顺序上可以确定的是:先把模型名换成当前名字,把这一类排掉,再回头怀疑凭证。
七、这篇覆盖不到的情况
本文只覆盖官方错误码表列出的那七个码,事实全部来自 DeepSeek 官方的错误码页、限速与隔离页和模型与价格页。它有一个很实际的边界:如果你的请求不是直连 api.deepseek.com,而是走了第三方网关、聚合平台或者中转服务,那么同一个码完全可能来自中间那一层,而不是 DeepSeek。 中转层自己也有余额、自己也有限速,它可以在你的 DeepSeek 账号余额充足时回你一个 402,也可以在远没到官方并发上限时回你 429。照着官方表去查,方向一开始就错了。
所以走中转的场景,第一步不是查表,而是先确认「这个码是谁返回的」:看响应体的字段结构像不像官方接口的格式、看响应头里有没有中间层留下的标识、或者把同一个请求直连官方端点打一次做对照。这一层官方文档不会替你考虑,代理商的文档也往往写得含糊。反过来说,判断一个中转值不值得用,有一条挺实用的标准:它是否把上游的错误码原样透传、以及出问题时你能不能拿到原始响应。想清楚直连与走中转的取舍,可以先看DeepSeek 接入的整体梳理。
最后留一句判断:401 和 402 值得单独写一篇,不是因为它们难,而是因为它们太容易被压缩成「调不通」这一个笼统的感受。工程上真正该改的,是把调用失败按码分流出去——401 告警给管凭证的人,402 告警给管付费的人,429 进限流队列而不是重试队列。做到这一步,这篇文章里的大部分内容,你以后都不需要再读第二遍。
想直接调 DeepSeek,不想折腾账号?
力达云一个 key 支持 OpenAI 与 Anthropic 两种格式,按上游定价换算,注册送 ¥5。