调用 DeepSeek API 时收到 HTTP 401,第一反应不应是怀疑服务端故障,而是确认客户端没有完成身份认证。401 是鉴权链路中最常见的失败信号,但导致它的环节往往不止一个。本文给出的排查闭环不依赖 DeepSeek 的某个内部错误码,而是基于 HTTP Bearer Token 鉴权的通用行为;DeepSeek API 开发者可以直接套用,其他标准 Bearer 接口也同样适用。
先区分 401 与 403
HTTP 状态码已经把问题边界划开:
- 401 Unauthorized:服务端没有从请求中得到可接受的身份凭据,或者凭据无效。
- 403 Forbidden:服务端已经识别身份,但该身份无权访问目标资源。
如果收到 401,排查重心应放在 API key、Authorization 请求头和密钥注入方式;如果收到 403,再检查 API key 权限范围、访问白名单、账号状态或资源授权。状态码混在一起,很容易在错误的层级上浪费时间。
第一步:用 curl 最小化复现
在进入业务代码前,先用 curl 构造一个只包含必要字段的请求,把问题边界压缩到鉴权本身:
curl -i --max-time 10 "${API_BASE_URL}/your/endpoint" \
-H "Authorization: Bearer ${API_KEY}"
-i 用于同时显示响应头和响应体。如果这一步已经返回 401,就不需要先怀疑业务逻辑,而是检查请求本身。记录响应体中的 error、message 或 request_id 字段;不同服务商字段命名不同,以实际响应为准。这些信息比状态码更能缩小范围,也方便后续向服务商提供排查上下文。
注意:不要用会自动重试的 SDK 直接做复现,否则一次 401 可能变成多次上游请求,反而干扰观察。
第二步:检查 API key 是否有隐形字符
从网页或聊天工具复制 API key,最容易带入空格、换行或不可见字符。可以先检查 key 的实际字节:
printf '%s' "${DEEPSEEK_API_KEY}" | od -An -c
如果输出中出现 \n 或多余空格,说明 key 在复制或注入时被污染。环境变量文件中的模板形如:
DEEPSEEK_API_KEY=
注意不要在值两侧加引号或空格,也不要让行尾残留 \r。在 Windows 环境下编辑过的 .env 文件尤其容易带入 \r。
第三步:确认环境变量进入目标进程
很多 401 问题并不是 key 失效,而是进程根本没有拿到 key。可以先在启动脚本中显式检查:
set -eu
if [ -z "${DEEPSEEK_API_KEY:-}" ]; then
echo "DEEPSEEK_API_KEY is not set" >&2
exit 1
fi
如果使用 .env 文件,需要确认加载时机在服务启动之前,并且加载方式正确。不要从 .env 读取后只在 shell 里临时生效,而在 systemd、容器或 CI 环境中漏配。不同系统的环境变量传递方式不同,排查时直接在目标进程中打印“是否为空”要比在终端里 env 更可靠。
第四步:确认 Authorization 头拼接方式
Bearer Token 鉴权最常见的失败点,是请求头没有按照约定拼写。可以用 curl -v 查看实际发送的请求头:
curl -v "${API_BASE_URL}/your/endpoint" \
-H "Authorization: Bearer ${API_KEY}"
需要确认几点:
- 协议头名称是
Authorization,不是authorization、Auth或X-Api-Key。 - 值由
Bearer、一个空格和 API key 组成。 - 服务端最终只收到一个 Authorization 头;如果 SDK 已经自动添加,而你又在拦截器中手动添加,可能出现重复请求头,部分服务端只取第一个或直接拒绝。
如果代码里使用自研 HTTP 封装,最容易漏的是 Bearer 与 key 之间的空格,或把 key 当 query 参数拼在 URL 上。这两种情况都可能导致 401。
第五步:检查代理或网关是否改写请求头
本机 curl 正常、部署到测试环境才失败时,问题往往不在 API key,而在中转链路。公司代理、API 网关、service mesh 或反向代理都可能对 Authorization 头做改写、剥离或重复注入。可以在网关访问日志或内网抓包中确认到达上游的 Authorization 是否保持为 Bearer。必要时在测试环境绕开代理直接请求上游,用二分法确认责任边界。
密钥轮换的工程闭环
排查完 401 之后,真正需要建立的是密钥轮换机制。一个可行的闭环如下:
- 先在服务商控制台或密钥管理系统中创建新 key,不要立刻删除旧 key。
- 用新 key 在本地运行最小 curl,验证可访问。
- 将新 key 写入目标环境:服务器环境变量、CI/CD secret、容器 secret 或密钥管理服务。
- 用同一份最小请求在目标环境验证新 key 生效,再逐步切换流量。
- 保留旧 key 观察一段时间,确认日志中没有旧 key 的鉴权失败后,再在控制台关闭或删除。
- 如果 key 曾经出现在代码仓库、日志或前端代码中,视为已泄露,应尽快作废,不能只做轮换。
从工程角度看,更重要的不是“把某个 key 换掉”,而是减少 key 的暴露面。可以把所有构造请求的代码收敛到一个公共模块,统一读取环境变量、统一拼接 Authorization 头,禁止在业务代码中硬编码 key。CI/CD 中的 key 使用 secret 注入,避免写进镜像或缓存层。日志打印时必须脱敏,只保留 key 后四位或完全隐藏。
诊断清单
- 收到 401 后,先用 curl 拿到原始状态码、响应头和 request_id。
- 检查 key 字符串中有无空格、换行、
\r。 - 确认环境变量在目标进程中存在,且值没有被引号包裹。
- 确认 Authorization 头为
Bearer,且没有重复或经过代理改写。 - 轮换后,确认旧 key 已从代码、镜像、CI 配置和密钥文件中清除。
这篇排查闭环没有依赖 DeepSeek 内部错误码格式,因此不会因为版本差异而过期。遇到 DeepSeek API 返回 401 时,按这个顺序排查,大多数鉴权问题都能在五分钟内定位到具体环节。