一个端点,
所有模型。
Tare 是挡在你所有模型前面的一层 OpenAI 兼容网关 —— 我们的,和你自己带来的。一个 base URL、一把 key,每一个 token 都算得清。
错误码
错误体是 OpenAI 形状({"error": {"message": …, "type": …}}),SDK 读的就是这个形状;
换成别的形状,客户端日志里会退化成一句 undefined。
| 状态码 | 含义 | 处理方式 |
|---|---|---|
| 401 | key 缺失、格式错误或已吊销 | 检查 header。401 只关于凭据本身 |
| 403 | 凭据有效,但这次不许过 | Key 类型与接口不匹配、额度用尽、或来源 IP 不在该 Key 的白名单上 —— 看 metadata.refusal_reason |
| 404 | 这个模型没有配置路由 | 改模型名、加一条路由或加一条兜底 |
| 402 | 账号欠费(仅此一种含义) | 充值,恢复是自动的 |
| 429 | 超过每分钟请求数 | 按 Retry-After 退避,见限流与配额 |
| 503 | 有路由,但渠 道全部失败 | 重试,不是配置问题 |
三种「余额不足」的区分
⚠️ 额度用尽是 403,不是 402。 402 只表示账号欠费。两个码的分工是「谁的问题」:402 是 账号对平台的欠款,403 是治理阈值(key 额度、月度预算)拦下的。
下表是唯一的权威口径:
| 情形 | HTTP | error.type / metadata.error_type | metadata.refusal_reason |
|---|---|---|---|
| 这把 key 的累计额度用尽 | 403 | token_limit_exceeded | key_credit_exhausted |
| 账号月度预算到顶 | 403 | token_limit_exceeded | budget_exhausted |
| 账号欠费 | 402 | payment_required | account_balance_insufficient |
| 调用来源不在该 key 的 IP 白名单上 | 403 | permission_denied | source_ip_not_allowed |
平台型客户要把原因转述给终端用户时,判断依据是 refusal_reason,不是文案:
refusal_reason | 含义 | 对终端用户的说法 |
|---|---|---|
key_credit_exhausted | 这把 key 的累计额度用完 | 「你的额度已用完」,充值可解决 |
budget_exhausted | 账号月度预算到顶 | 与他无关,不要引导充值 |
account_balance_insufficient | 账号欠费 | 与他无关,不要引导充值 |
source_ip_not_allowed | 来源地址不在这把 key 的名单上 | 与钱无关:换一台在名单上的机器重试,或请 key 的持有者把这台加进名单 |
中间两种若说成「你的额度用完了」,用户会为与自己无关的事付费;最后一种说成额度问题, 他会去给一个根本不缺钱的账号充值。
{"error":{"code":403,"type":"token_limit_exceeded","message":"...","metadata":{
"error_type":"token_limit_exceeded",
"refusal_reason":"key_credit_exhausted"}}}
两种响应信封
| 接口 | 成功 | 失败 |
|---|---|---|
/v1/chat/completions、/v1/keys/*、/v1/models、/v1/generation | OpenAI 形状 | HTTP 非 2xx + {"error":{…}} |
/v1/integration/*、/v1/content-settings* | {"code","message","data","success"} | HTTP 非 2xx,body 仍是这个信封(不是 200 + success:false) |
⚠️ 经由 api.jamerly.dev 网关访问对账接口时,网关不拆上游信封,业务数据会嵌两层
(data.data);直连 tare.jamerly.ai 只有一层。判据是 data 里是否又套了一个
code/success,而不是部署位置——两种部署下这条都成立。
错误体的形状
{"error":{
"code": 403,
"type": "token_limit_exceeded",
"message": "...",
"metadata": {
"error_type": "token_limit_exceeded",
"refusal_reason": "key_credit_exhausted",
"provider_code": "503",
"provider_raw": "{\"error\":{\"code\":\"model_not_found\",\"message\":\"...\"}}"
}}}
| 字段 | 保证 |
|---|---|
error.code | 始终等于 HTTP 状态码,按它分支是安全的 |
error.type | 始终存在,OpenAI SDK 读这一格 |
metadata.error_type | 与 error.type 同值,有些 SDK 只读这一格 |
metadata.refusal_reason | 只在上述三种拒绝时出现 |
metadata.provider_code | 上游原始状态码,透传,可能缺失 |
metadata.provider_raw | 上游回包原文,未做任何改动。 不解析、不摘要、不过滤。仅在请求未发出时不存在该字段 |
[!NOTE]
error.message由 Tare 生成:说明这属于哪一类失败、上游返回了什么状态码。 Tare 不解析上游的错误体来生成该字段 —— 各家错误结构不一致,推断哪个字段承载说明 信息一旦出错不会报错,只会产生一段措辞确定但内容错误的描述。上游返回的原文原样保留在
metadata.provider_raw中,可直接用于排查,或原样提供给 源厂商。唯一的例外:失败源于本次请求本身时(工具 schema 有误、参数越界),message中会一并带上上游的解释 —— 仅返回「请求无效」不足以定位问题。
[!NOTE] 顶层
type与metadata.error_type是同值、刻意重复:两边生态读的不是同一格。 只给后者时,按 OpenAI SDK 写的客户端会拿到 undefined——不报错,只是把一个明确的拒绝 显示成「未知错误」 。
error_type的取值保持不变,细分是新增在refusal_reason上的。
404 与 503 的区分
「没配路由」改配置能解决,「上游全部失败」等待能解决。合并成一个状态码会把人引向错误的 方向,这也是兜底路由只补 404 一侧、不补 503 一侧的原因。
[!NOTE] 这里曾经是 402。402 表示「付款后可继续」,而这个错误付多少钱都不会好——客户会先充值、 再查余额,最后才发现是模型名写错了。
有路由但模态不匹配
返回 404,但消息不同:配置的上游不接受图片(或音频、视频)。解决方式是在路由上声明模态, 或改用支持该输入的模型。