OpenRouter 报错 401 是什么原因?鉴权失败的排查顺序

2026-08-31
站内工具 AI 编程工具报错分诊器 → 把报错原文贴进去,先分清是网络、额度、配置还是上游故障,再决定往哪个方向查。

数据截至 2026-08,价格与限额以各官网为准。本文只讲计费与接入机制,不列具体价格数字。

先把结论说完:OpenRouter 的 401 只有一个含义,就是鉴权没通过,官方的原话是 API key 无效或缺失,让你确认 key 正确并且确实放进了 Authorization 头。 它不代表你没钱、不代表你没权限、也不代表你被限流了——这三种情况在 OpenRouter 那里各有各的状态码和各自的 error_type,混淆它们是排查时最常见的时间浪费。本文做两件事:第一,把 401 和它最容易被认错的三个邻居彻底分开;第二,给出一个从”错误是不是真的 401”开始、逐层往下的排查顺序。

第一步:先确认它真的是 401

这一步看起来多余,但它能省掉后面所有的无用功。OpenRouter 的错误响应里除了 HTTP 状态码,还有一个更精确的 error_type 字段,官方按类别把它们分成了几组。和鉴权相关的那一组是这样的:

  • authentication —— API key 缺失、无效,或者已被吊销
  • permission_denied —— key 是有效的,但缺少所需权限,或者请求被 guardrail 拦截
  • payment_required —— 账户或该 key 的额度不足

三者的区别值得单独强调一遍,因为它们的处理方向完全相反:

你看到的error_type真实含义该往哪查
401authenticationkey 本身不被认查 key 字符串、查请求头
403permission_deniedkey 认了,但这件事不让你做查权限配置、查 guardrail
402payment_requiredkey 认了,权限也有,就是没额度查账户余额、查 key 上的消费上限

特别提醒 403:它的官方定义是权限不足或被 guardrail 拦截,不是很多人想当然以为的”地区限制”。按地区去排查 403,方向从一开始就是错的。

如果你的客户端只把状态码打出来、没打 error_type,第一件该做的事是把响应体完整打印出来。官方的错误响应结构里带着 metadata.error_type,有了它,后面的排查能少走一大半弯路。

第二步:区分 key 缺失、无效、已吊销

官方把这三种情形都归在 authentication 下面,但它们的成因和解法并不一样。

缺失是最容易发生也最容易忽略的一种。它未必是你没写鉴权头,更常见的形态是环境变量在当前进程里是空的——比如你在一个 shell 里设置了变量,却在另一个 shell 或另一个服务进程里发的请求;或者变量名拼错了,读出来是空字符串,最后拼成了一个只有 Bearer 前缀、后面什么都没有的请求头。判断方法很直接:在发请求之前先把你要用的那个变量本身打印出来看长度,而不是相信它应该有值。

无效指 key 字符串本身不被接受。除了复制粘贴时漏字符、带上了首尾空格或换行这类问题,还有一种情况是你用错了账户——同一个人在个人账户和组织账户下各建过 key,拿了 A 账户的 key 去调 B 账户配额下的资源。

已吊销是官方在定义里专门列出来的第三种,也是最容易被漏掉的一种。key 被删除或轮换之后,旧 key 就不再有效,但你的某个服务、某台机器、某个定时任务可能还在用旧的那份。这种故障有个典型特征:大部分调用是好的,只有某一个入口在报 401。如果你遇到的是这种”部分失败”,几乎可以直接跳到下一节。

第三步:核对 Authorization 头的形式

官方示例里给出的鉴权头形式是 Authorization: Bearer <OPENROUTER_API_KEY>,也就是”Bearer + 一个空格 + key”。对着这个标准逐项核对,比凭印象改要快:

  • 头的名字是 Authorization,不是 Auth,也不是 X-Api-Key
  • Bearer 和 key 之间是一个空格,前后没有引号
  • key 本身没有被换行截断——从网页复制长字符串时,这件事发生的概率比你以为的高

另外有一个值得澄清的点:官方示例里还出现了 HTTP-RefererX-OpenRouter-Title 两个头,文档明确说明它们是可选的,作用是让你的应用出现在 OpenRouter 的榜单上。它们和鉴权没有关系,缺了不会导致 401,加了也不能解决 401。排查时不必在它们身上花时间。

如果你用的是 OpenAI 官方 SDK 指向 OpenRouter 的方式(官方支持这种 drop-in 用法,把 baseURL 设为 OpenRouter 的 v1 地址即可),那么 key 是通过 SDK 的 apiKey 参数传的,鉴权头由 SDK 自己拼装。这种情况下要检查的不是头本身,而是你传给 SDK 的那个值——以及一个很隐蔽的点:SDK 通常会在你没显式传参时去读它自己约定的环境变量,如果那个变量里还留着别家平台的 key,SDK 会把它拿去用,结果就是一个让人摸不着头脑的 401。显式传参可以彻底避免这类问题。

第四步:如果是轮换 key 之后开始报的

OpenRouter 官方文档有专门讲 key 轮换的一节,给出的顺序是三步:先创建新 key,再更新所有应用,最后才删除旧 key

这个顺序不能颠倒,原因就是上面说的”部分失败”。如果你先删旧 key 再去改配置,那么在两个动作之间的所有请求都会 401;而如果你有多个服务共用一份 key,很可能改完了主服务、忘了某个后台任务,等到几小时后那个任务被触发才暴露出来。

所以当你面对的是”轮换之后开始报 401”,排查方向不是去研究 key 对不对,而是去清点还有谁在用旧的那份。按部署单元逐个过:主服务、后台任务、定时脚本、本地开发环境、CI 配置、以及某个同事机器上的 .env

顺带一提,OpenRouter 支持给单个 key 配置消费上限,查询 key 信息的接口会返回上限、重置周期和剩余量这几个字段。这个接口在排查时有个额外用途:它能同时验证 key 的有效性和它的额度状态——如果调它就返回鉴权错误,说明问题确实在 key 本身;如果它正常返回、只是剩余量见底,那你面对的其实是 402 不是 401。关于 key 的日常管理,站内还有一篇API Key 安全管理可以参考。

第五步:第三方客户端与网关场景

如果你不是直接发 HTTP 请求,而是通过某个桌面客户端、编辑器插件或者自建网关在用 OpenRouter,401 的产生位置就多了一层。

关键在于先定位是哪一段在报错。最简单的办法是绕开中间层:用一个最小的直连请求(把 key 放进 Authorization 头,调一个最简单的端点)验证 key 本身。如果直连是通的,那问题就在中间层的配置里,而不在 key 上;如果直连也 401,那就回到前面几步。

自建网关的场景还要多留意一层:网关上通常有它自己的一套令牌体系,你的客户端拿的是网关发的令牌,网关再拿真实的 OpenRouter key 去请求上游。这时候”401”可能来自两个完全不同的地方,看错了就会一直在错误的一侧打转。判断依据是错误响应的形态——上游返回的错误会带着 OpenRouter 的响应结构和 error_type 字段,网关自己拒绝的通常不会。想了解这类架构的取舍,可以看站内的API 中转的安全考量

排查顺序小结

把上面的内容压成一条可以照着走的顺序:

  1. 打印完整响应体,确认 error_type 是不是 authentication。是 payment_requiredpermission_denied 就走别的路
  2. 打印你实际用的 key 变量,确认它非空、长度正常、首尾无空白
  3. 对照官方形式核对请求头Authorization: Bearer <key>,一个空格,无引号
  4. 如果是部分入口失败,按部署单元清点谁还在用旧 key
  5. 用 key 查询接口验证:能正常返回说明 key 有效,问题在别处;返回鉴权错误说明确实是 key 的问题
  6. 如果经过中间层,先用最小直连请求把两侧分开

最后提醒一个反直觉的现象,它经常被误判成鉴权问题:账户余额为负时,请求有可能失败,而且这种失败会波及免费模型。官方限额说明的措辞是「可能会看到错误,包括免费模型」,解法是把余额补到零以上。如果你某天发现所有模型突然全都调不通、包括原本免费的那些,先去看余额,别一头扎进 key 里。这属于 402 的范畴,具体可以对照OpenRouter 额度管理那篇。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。

留言讨论

评论发布后会被人工复核,违规内容将被删除。

    还没有人评论,来说说你的看法

    如果发表没有反应,可以前往联系我们告诉我们。

    OpenRouter 充值不方便?

    国内直连的 OpenAI 兼容端点,一期提供 DeepSeek,注册送 ¥5。

    看替代方案

    这个页面有问题?

    提交时会附带当前页面地址和浏览器信息,帮助我们定位问题。不填联系方式即为匿名。