Gemini API 401 报错:API key not valid 的成因与修法

先分清三种长得像 401 的返回体

客户端弹的 401 和上游返回的 400 是两套话术

把 Gemini 的 key 填进 OpenAI 兼容客户端,报错框里出现的往往是这一段:

{"error":{"message":"API key not valid. Please pass a valid API key.","type":"invalid_request_error","code":401}}

同一句话在 Google 原生接口里对应的其实是 HTTP 400 加 INVALID_ARGUMENT,到了 OpenAI 兼容层才被改写成 401,因为 OpenAI 生态把这类问题归进鉴权一档。搞清楚这层改写关系,你就不会拿着 401 去翻权限设置,真正要修的是 key 本身。

完全没带凭据时用的是另一套词

请求里完全没带凭据,看到的通常是这一行:

{"error":{"code":401,"message":"No API key provided. Set the Authorization header."}}

两段原文一对比就分开了:出现 API key not valid 说明 key 传上去了但内容不对;出现 No API key provided 说明请求头根本没带上,问题出在客户端配置或代理转发环节。

还有一种 401 跟 key 毫无关系

自己搭了反向代理时,如果网关配了 Basic 认证,客户端会先撞上网关的 401,返回体多半是一整段 HTML 而不是 JSON。看到返回内容以 <html> 开头,就该去查 nginx 或网关配置,跟模型 key 没有半点关系。判断依据只有一条:返回体是不是 JSON。

逐条对照:401 的八个真实成因

复制 key 时多带了空白字符

从网页上复制 key 是高频踩的坑。行尾的换行符会被一起复制走,写进配置文件后表现为 key 存在但死活不认。判断方法很直接:打印 key 的长度,或者用 repr() 看一眼有没有 \n,五个字符的差异足以让你查一晚上。

八条成因自查清单

用一个小脚本定位到具体字符

把 key 读进来,逐字符检查 ASCII 范围和长度,比肉眼找空格可靠得多:

import os

key = os.environ.get("GEMINI_API_KEY", "")
print("长度:", len(key))
print("两端是否有空白:", key != key.strip())
bad = [(i, repr(c)) for i, c in enumerate(key) if not (c.isalnum() or c in "-_")]
print("异常字符:", bad[:10])

正常 key 打印出来的异常字符列表是空的;如果有内容,那就是你要删掉的东西。

把这段脚本接进服务启动流程,每次启动打印一行校验结果,出问题时一眼就能看到 key 是否被污染。这个动作成本很低,省下的却是反复来回排查的时间。

如果打印出的长度明显短于正常值,多半是复制时被截断了。有的输入框有长度限制,有的文本框会把某些字符当成变量吞掉,这种情况改成写进配置文件再由程序读取,比在界面上反复粘贴稳妥。

五分钟定位:三条命令走完排查

命令一:用参数方式直连原生接口

curl -s -o /dev/null -w '%{http_code}\n' \
  "https://generativelanguage.googleapis.com/v1beta/models?key=$GEMINI_API_KEY"

返回 200 说明 key 本身有效,问题在客户端配置;返回 400 或 401 说明 key 需要重新生成。

命令二:换成请求头方式复测

curl -s -X POST \
  "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contents":[{"parts":[{"text":"ping"}]}]}' | head -c 400

两种传法结果不一致,说明你的运行环境或某个中间层对其中一种写法不兼容,改用另一种即可。

命令三:换成聚合入口做对照

把 base_url 指向 https://xyuapi.top/v1(备用 https://xyuai.cc/v1),用同一把平台 key 复测一次。如果这里通过、直连失败,方向就很清楚了:是网络可达性或项目侧状态问题,而不是你写的代码有问题。

返回体对照表

返回体片段真正含义你该做什么
API key not valid. Please pass a valid API key.key 内容不正确重新复制,清掉空格换行引号
No API key provided请求没带凭据检查客户端字段与代理转发
API key expiredkey 已过期或被删除在控制台重新生成一把
PERMISSION_DENIED 并附带项目信息项目状态或接口启用问题检查项目设置与结算状态
<html> 开头的返回撞上了网关自己的登录页查 nginx 与网关配置

Python 侧的正确写法

走 OpenAI 兼容入口

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["XYU_API_KEY"].strip(),
    base_url="https://xyuapi.top/v1",
    timeout=120,
)

resp = client.chat.completions.create(
    model="gemini-2.5-pro",
    messages=[{"role": "user", "content": "用一句话解释什么是 API"}],
)
print(resp.choices[0].message.content)

走原生 SDK

import os
from google import genai

client = genai.Client(api_key=os.environ["GEMINI_API_KEY"].strip())
resp = client.models.generate_content(model="gemini-2.5-pro", contents="ping")
print(resp.text)

加载环境变量时的两个动作

读取后统一 .strip(),把复制带进来的空白字符清掉,这一步能消掉相当一部分 401。进程启动时再打印 key 的前四位、后四位和总长度,方便肉眼核对,但不要把完整 key 写进日志文件。

客户端填错字段的典型现场

五款常用客户端的正确填法

客户端该填什么高频错填
Chatbox自定义提供方填域名与密钥域名后面多带了 /v1beta
Cherry Studio提供方选 OpenAI 兼容并填 base_urlkey 里混进了 Bearer
NextChat设置里的接口地址与密钥地址少写了 /v1
ClineProvider 选 OpenAI CompatibleBase URL 结尾多了斜杠
Continueconfig.json 里的 apiBase把原生路径塞进兼容入口

地址到底该写到哪里

OpenAI 兼容协议下,base_url 写到 /v1 为止,后面的 /chat/completions 由客户端自己拼。多写一层路径,轻则 404,重则被网关判定为非法请求。

填法必须和 key 的类型配对

原生 key 配原生地址,平台 key 配平台入口。混搭属于高频的一类 401,尤其是从别人截图里抄配置的时候,地址抄对了、key 抄的是另一家的。

再补一个容易被忽略的细节:部分客户端把 key 存在浏览器本地存储里,清缓存、换浏览器、换设备之后就没了,表现成「昨天还好好的,今天就不行」。配置完成后把地址与 key 记进团队文档,别只留在某一台电脑上。

环境变量名也要统一。同一个项目里同时出现几个含义相同但名字不同的变量时,很容易改了一处漏掉另一处,收口成一个名字更省事。

客户端升级之后字段位置发生调整也是常事,升级完先跑一次简短请求验证,别等到批量任务开跑才发现填错了位置。

401 与 403、400 的边界

三个状态码各自的分工

400 是请求本身不合法,403 是身份有效但没有权限,401 是身份没通过。Gemini 原生接口会把不合法的 key 也返回 400,这就是容易误判的根源,只看数字会跑偏。

判断顺序按这个走

先看有没有 PERMISSION_DENIED,有就是权限或项目状态;没有再看 message 里是 key 无效还是完全没带凭据;两者都不是,再回头检查路径与字段名。按这个顺序排查,通常两三轮就能定位。

别只盯 HTTP 状态码

聚合接入平台和各家 SDK 对状态码的映射习惯不同,可靠的判断依据是 message 原文。把整段返回体复制出来贴进笔记,比盯着数字猜要快得多,也更方便下次直接搜到。

修完仍然不通过怎么办

进程必须重启

改完环境变量,运行中的进程读到的还是旧值。Python 服务要在调用时读 os.environ[...],而不是在模块顶部读一次存成常量;容器部署还要确认变量真的透传进去了,进容器执行 env | grep KEY 就能看到。

多套环境里 key 混着用

本地测试的 key、服务器上的 key、客户端里填的 key 分散在三处,改了一处没改另一处,表现就是时好时坏。收口到一个配置源,改一次全部生效,这类问题会直接消失。

在调用封装里加兜底告警

判断状态码,遇到 401 立刻抛出带上下文的异常,把 base_url、模型名、key 前后四位一起打出来。下次再遇到同样的报错,日志一眼就能看出是哪一层出的问题。

顺带把耗时也记下来。401 往往伴随用户反复点击重试,短时间内出现几十次完全相同的请求,日志里看到这种密集模式,就说明有人在界面上一直点按钮,而不是接口真的坏了。

同一把 key 在 A 机器上正常、在 B 机器上失败时,直接比较两台机器打印出来的 key 长度和首尾四位,差异一眼可见,比逐行读配置文件快得多。这类问题多半出在部署脚本上,而不是客户端本身。

稳定的接入方式与价格参考

接口信息与协议

主入口 https://xyuapi.top/v1,备用 https://xyuai.cc/v1,另支持 Gemini 原生 /v1beta 路径。协议是 OpenAI 兼容的,Chatbox、Cherry Studio、NextChat、Cline 这类客户端填上地址和 key 就能跑起来,不用改代码结构。

按次计费意味着什么

平台按一次请求固定价收费,一次请求塞多长的输入都按同一个价格算。这点和按 token 计费的直觉不一样,批量处理长文档时账更好算:两千条数据就是两千次调用,价格一眼能估出来。

常用模型价格参考

模型价格适用场景
gemini-2.5-pro0.031 元/次长文档理解、结构化抽取
gemini-3-pro-preview0.05 元/次复杂推理、代码生成
gemini-3.1-pro-preview0.09 元/次高难度任务、深度分析
claude-sonnet-4-5-thinking0.09 元/次带思考链的长文写作
NanoBanana-Pro(生图)0.27 元/次图片生成与图片编辑

现在的动作清单

  1. 用上面三条 curl 命令确定失败发生在哪一层,把结论写进笔记。
  2. 把 key 统一收口到一个配置源,读取时 .strip(),并打印长度做校验。
  3. 客户端地址改到 /v1 为止,确认 key 与地址是同一家的。
  4. 加一段状态码兜底日志,把 base_url 与模型名一起输出,方便下次一眼定位。

相关阅读

🚀 想要立即使用?来小鱼API体验全系列AI模型

查看全部产品

支持Gemini / Claude / GPT / Grok / DeepSeek · 国内直连 · 支付宝/微信