调用 DeepSeek API 时持续收到 401通常不是密钥本身失效而是鉴权信息在传递链路的某个环节被丢失或改写。本文从工程配置层面梳理排查顺序帮助定位问题。一、先确认密钥是否真正加载最常见的情况是代码读取了环境变量但变量并未注入进程。排查时不要只看.env文件是否存在而要在运行时打印变量长度和前后几位不要打印完整密钥import os key os.getenv(DEEPSEEK_API_KEY) print(loaded:, bool(key), len:, len(key) if key else 0) print(prefix:, key[:6] if key else None)如果loaded为 False检查启动命令是否在正确的 shell 或容器环境中执行.env是否被实际加载许多框架不会自动读取容器编排中环境变量是否注入到应用容器而非构建阶段变量名是否拼写一致大小写敏感。二、密钥格式校验拿到密钥后先做基本格式检查。DeepSeek API Key 通常以固定前缀开头若前缀明显不符说明取到了错误的值例如取到了其他服务的 key或复制时带入了引号、空格、换行。key key.strip().strip().strip() assert key.startswith(sk-), key prefix mismatch注意不要用assert在生产代码中做校验这里仅用于本地排查。三、Authorization 请求头拼装DeepSeek API 使用 Bearer 鉴权请求头格式为Authorization: Bearer API_KEY手动构造请求时容易犯两类错误漏掉Bearer前缀只写了密钥前缀与密钥之间缺少空格或多了空格。headers { Authorization: fBearer {key}, Content-Type: application/json, }如果使用requests注意不要同时传入auth参数和Authorization头两者可能冲突导致最终头被覆盖。四、代理与网关中的头覆盖在服务端部署时请求往往经过 Nginx、API 网关或公司内网代理。这些中间层可能丢弃Authorization头重写为其他鉴权头在转发时追加自己的鉴权信息覆盖原始值。排查方法是在代理层打印最终转发出去的头注意脱敏或临时绕过代理直连验证。如果直连正常、经代理失败问题基本在中间层。五、SDK 默认配置与手动 HTTP 调用的差异使用官方 SDK 时鉴权头由 SDK 自动注入通常只需在初始化时传入api_key。手动 HTTP 调用则需要自己拼装。两者行为不一致时重点检查SDK 初始化时传入的 key 是否为空或为占位符SDK 是否读取了与手动调用不同的环境变量名SDK 版本是否过旧导致请求路径或头格式与当前 API 不匹配。建议先用最小手动 HTTP 请求验证密钥有效性再回到 SDK 排查配置。import requests resp requests.post( https://api.deepseek.com/chat/completions, headers{Authorization: fBearer {key}}, json{ model: deepseek-chat, messages: [{role: user, content: ping}], }, timeout30, ) print(resp.status_code, resp.text[:200])六、可复用的鉴权配置检查清单运行时确认环境变量已加载长度与预期一致去除密钥首尾空白、引号、换行确认前缀符合预期请求头为Authorization: Bearer key空格正确未同时使用auth与手动Authorization头代理/网关未丢弃或覆盖鉴权头SDK 初始化参数与环境变量名一致先用最小手动请求验证再排查 SDK401 响应体中的错误信息可用于区分“密钥无效”与“头缺失”更换密钥后重启进程避免旧值缓存。七、边界与失败模式密钥轮换后未重启服务进程仍持有旧密钥多环境共用同一变量名测试环境覆盖了生产值日志中打印了完整密钥导致泄露后被动失效代理层对Authorization做了大小写归一化某些实现可能不兼容。按上述顺序排查多数持续 401 都能定位到具体的配置环节。