Permalink to 错误与限流错误与限流
所有接口——/v1、/v1beta,以及不需要认证的那几个端点——的错误都用同一个响应体:
{
"error": {
"message": "invalid api key",
"type": "authentication_error",
"code": "invalid_api_key",
"param": null
}
}| 字段 | 用途 |
|---|---|
code | 按它分支。稳定、机器可读、足够具体 |
type | OpenAI 兼容的分类,给已经在按它 switch 的客户端 |
message | 给人看的。已脱敏,但是自由文本,不属于契约 |
param | 目前恒为 null,为兼容 OpenAI SDK 而保留 |
type 的取值范围是 invalid_request_error、authentication_error、permission_error、
not_found_error、rate_limit_error、insufficient_quota、upstream_error、timeout_error、
server_error。
message 在离开我们之前会被脱敏:上游名称、base URL、上游模型名和凭据都不会出现在里面。上游的
原始文本只留在我们的日志里——这也是为什么提工单时附上 X-Request-Id 比粘贴 message 有用得多。
Permalink to 状态码状态码
| 状态 | 什么时候 |
|---|---|
400 | 请求本身有问题——缺字段、枚举值不对、URL 不可用 |
401 | 没有凭据、Key 未知或被禁用、账号被停用 |
402 | 额度不够 |
403 | OAuth token 缺少这条路由所需的 scope |
404 | 路由不存在、模型不存在,或任务不属于你 |
409 | 幂等键冲突,或在产物就绪前请求了 /content |
413 | 请求体或上传文件超过上限 |
425 | 同一个幂等键的请求仍在准备中,稍后重试 |
429 | 触发速率限制、并发限制,或在途任务过多 |
499 | 调用方在我们完成之前断开了 |
500 | 我们的 bug |
502 | 上游失败或返回了不可用的内容 |
503 | 当前没有可用上游,或我们的数据库短暂不可读 |
504 | 上游没有在限定时间内应答 |
注意 402 和 503 的差别:402 需要充值,重试没有意义;而来自 no_available_provider 或
precheck_failed 的 503 应该退避重试——它表示我们这边暂时服务不了,不是你的请求写错了。
Permalink to 限流限流
两条独立的限制,都是按 Key 计算:
| 限制 | 默认值 | 触发时 |
|---|---|---|
| 每分钟请求数 | 60 | 429 rate_limited,Retry-After: 60 |
| 并发请求数 | 15 | 429 too_many_concurrent_requests,Retry-After: 1 |
表中是默认值,具体部署可能调高。两者分别用令牌桶和信号量实现——超限直接拒绝,不排队。 OAuth access token 没有对应的 Key 行,因此按账号计算。
第三条限制只针对异步媒体:一个账号最多 64 个 queued 或 running 的任务,跨所有 Key 和会话
合并计算。超过返回 429 media_open_job_capacity,带 Retry-After: 30,见
异步任务。
**没有 X-RateLimit-* 响应头。**请读 Retry-After 并退避。
Permalink to 怎么安全地重试怎么安全地重试
429、503、502、504:指数退避加抖动重试;有Retry-After就遵守它。- 除
429外的4xx:不要重试,重试的结果完全一样。 - 创建任务:带上
Idempotency-Key。用同一个键重试会拿回原来那个 Job,而不是创建并计费第二个。 - 对话流式:第一个字节之后失败的流我们无法替你重试,HTTP 状态码会停在
200。请检查每一个 SSE 帧里有没有error键,需要的话由你自己重发整个请求。
Permalink to 所有路由通用的错误码所有路由通用的错误码
code | 状态 | 含义 |
|---|---|---|
missing_token | 401 | 没找到任何凭据 |
invalid_api_key | 401 | 这把 Key 不存在 |
key_disabled / key_expired | 401 | Key 不可用 |
account_disabled | 401 | 所属账号被停用 |
insufficient_scope | 403 | OAuth token 缺少该模态的 scope |
insufficient_credits | 402 | 余额不足以覆盖这次调用 |
no_account | 401 | 凭据解析不到可计费的账号 |
precheck_failed | 503 | 额度账本短暂不可读 |
rate_limited | 429 | 每分钟请求数超限 |
too_many_concurrent_requests | 429 | 并发请求数超限 |
model_not_found | 404 | 这把 Key 下该模型不可用于此操作 |
no_capable_provider | 404 | 有候选上游,但没有一家实现这个能力 |
no_available_provider | 503 | 所有候选上游都在冷却中 |
request_timeout | 504 | |
request_canceled | 499 | 客户端断开 |
not_found | 404 | 没有路由匹配该方法与路径 |
internal_error | 500 | 我们的 bug——请附上 request id |
各端点特有的错误码见对应页面: 图片、 视频、 音频、 Chat、Gemini、 异步任务。
Permalink to 故障转移以及它对你意味着什么故障转移,以及它对你意味着什么
一个上游失败的请求会在你看到错误之前先在另一个上游重试,所以一个 502 表示所有候选都失败了,
不只是第一个。两条边界:
- 流式响应在第一个字节上线之后不再故障转移;
- 客户端错误永不重试,因为重试一个写错的请求只会放大负载。
除了可能增加的延迟之外,这个过程对响应是不可见的。请把 /v1/chat/completions 的客户端超时设宽
一些——某些 HTTP 客户端默认的 10 秒连一次长回复都不够,更别说一次故障转移。
Permalink to 健康检查健康检查
有一个不需要认证的端点,用来判断网关是否还能服务:
curl https://hypit.ai/healthz{
"status": "ok",
"version": "v0.4.2",
"db": "ok",
"outbox_pending": 0,
"cleanup_running": true,
"cleanup_pending": 12,
"cleanup_oldest_due_age_s": 0
}值得关心的是 status 和 db 两个字段,降级时整体返回 503。outbox_* 与 cleanup_* 是运维计数
器,可能随时变化。它适合做负载均衡探针或状态页,不要拿它做每次请求前的预检。