把 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 说明请求头根本没带上,问题出在客户端配置或代理转发环节。
自己搭了反向代理时,如果网关配了 Basic 认证,客户端会先撞上网关的 401,返回体多半是一整段 HTML 而不是 JSON。看到返回内容以 <html> 开头,就该去查 nginx 或网关配置,跟模型 key 没有半点关系。判断依据只有一条:返回体是不是 JSON。
从网页上复制 key 是高频踩的坑。行尾的换行符会被一起复制走,写进配置文件后表现为 key 存在但死活不认。判断方法很直接:打印 key 的长度,或者用 repr() 看一眼有没有 \n,五个字符的差异足以让你查一晚上。
\n、\r\n、首尾空格,配置读取时没有 strip() 就会原样发出去。Bearer ,有的客户端自动补,两边都加变成 Bearer Bearer AIza...,必然失败。.env 写成 GEMINI_API_KEY="AIza...",某些读取方式会把引号当成值的一部分,标准写法是不加引号。把 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 expired | key 已过期或被删除 | 在控制台重新生成一把 |
PERMISSION_DENIED 并附带项目信息 | 项目状态或接口启用问题 | 检查项目设置与结算状态 |
以 <html> 开头的返回 | 撞上了网关自己的登录页 | 查 nginx 与网关配置 |
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)
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_url | key 里混进了 Bearer |
| NextChat | 设置里的接口地址与密钥 | 地址少写了 /v1 |
| Cline | Provider 选 OpenAI Compatible | Base URL 结尾多了斜杠 |
| Continue | config.json 里的 apiBase | 把原生路径塞进兼容入口 |
OpenAI 兼容协议下,base_url 写到 /v1 为止,后面的 /chat/completions 由客户端自己拼。多写一层路径,轻则 404,重则被网关判定为非法请求。
原生 key 配原生地址,平台 key 配平台入口。混搭属于高频的一类 401,尤其是从别人截图里抄配置的时候,地址抄对了、key 抄的是另一家的。
再补一个容易被忽略的细节:部分客户端把 key 存在浏览器本地存储里,清缓存、换浏览器、换设备之后就没了,表现成「昨天还好好的,今天就不行」。配置完成后把地址与 key 记进团队文档,别只留在某一台电脑上。
环境变量名也要统一。同一个项目里同时出现几个含义相同但名字不同的变量时,很容易改了一处漏掉另一处,收口成一个名字更省事。
客户端升级之后字段位置发生调整也是常事,升级完先跑一次简短请求验证,别等到批量任务开跑才发现填错了位置。
400 是请求本身不合法,403 是身份有效但没有权限,401 是身份没通过。Gemini 原生接口会把不合法的 key 也返回 400,这就是容易误判的根源,只看数字会跑偏。
先看有没有 PERMISSION_DENIED,有就是权限或项目状态;没有再看 message 里是 key 无效还是完全没带凭据;两者都不是,再回头检查路径与字段名。按这个顺序排查,通常两三轮就能定位。
聚合接入平台和各家 SDK 对状态码的映射习惯不同,可靠的判断依据是 message 原文。把整段返回体复制出来贴进笔记,比盯着数字猜要快得多,也更方便下次直接搜到。
改完环境变量,运行中的进程读到的还是旧值。Python 服务要在调用时读 os.environ[...],而不是在模块顶部读一次存成常量;容器部署还要确认变量真的透传进去了,进容器执行 env | grep 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-pro | 0.031 元/次 | 长文档理解、结构化抽取 |
| gemini-3-pro-preview | 0.05 元/次 | 复杂推理、代码生成 |
| gemini-3.1-pro-preview | 0.09 元/次 | 高难度任务、深度分析 |
| claude-sonnet-4-5-thinking | 0.09 元/次 | 带思考链的长文写作 |
| NanoBanana-Pro(生图) | 0.27 元/次 | 图片生成与图片编辑 |
.strip(),并打印长度做校验。/v1 为止,确认 key 与地址是同一家的。