# 错误与限流 (https://hypit.ai/zh/api-reference/errors/)

> 全站统一的错误信封、各状态码的含义，以及两条限流规则。

所有接口——`/v1`、`/v1beta`，以及不需要认证的那几个端点——的错误都用同一个响应体：

```json
{
  "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 有用得多。

## 状态码 [#状态码]

| 状态    | 什么时候                        |
| ----- | --------------------------- |
| `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` 应该退避重试——它表示我们这边暂时服务不了，不是你的请求写错了。

## 限流 [#限流]

两条独立的限制，都是按 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`，见
[异步任务](/zh/api-reference/jobs#并发上限)。

\*\*没有 `X-RateLimit-*` 响应头。\*\*请读 `Retry-After` 并退避。

## 怎么安全地重试 [#怎么安全地重试]

* **`429`、`503`、`502`、`504`**：指数退避加抖动重试；有 `Retry-After` 就遵守它。
* **除 `429` 外的 `4xx`**：不要重试，重试的结果完全一样。
* **创建任务**：带上 `Idempotency-Key`。用同一个键重试会拿回原来那个 Job，而不是创建并计费第二个。
* **对话流式**：第一个字节之后失败的流我们无法替你重试，HTTP 状态码会停在 `200`。请检查每一个 SSE
  帧里有没有 `error` 键，需要的话由你自己重发整个请求。

## 所有路由通用的错误码 [#所有路由通用的错误码]

| `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  |

各端点特有的错误码见对应页面：
[图片](/zh/api-reference/images#这两条路由特有的错误)、
[视频](/zh/api-reference/video#这条路由特有的错误)、
[音频](/zh/api-reference/audio#这三条路由特有的错误)、
[Chat](/zh/api-reference/chat#错误)、[Gemini](/zh/api-reference/gemini#错误)、
[异步任务](/zh/api-reference/jobs#查询类错误)。

## 故障转移，以及它对你意味着什么 [#故障转移以及它对你意味着什么]

一个上游失败的请求会在你看到错误之前先在另一个上游重试，所以一个 `502` 表示**所有**候选都失败了，
不只是第一个。两条边界：

* **流式**响应在第一个字节上线之后不再故障转移；
* **客户端错误**永不重试，因为重试一个写错的请求只会放大负载。

除了可能增加的延迟之外，这个过程对响应是不可见的。请把 `/v1/chat/completions` 的客户端超时设宽
一些——某些 HTTP 客户端默认的 10 秒连一次长回复都不够，更别说一次故障转移。

## 健康检查 [#健康检查]

有一个不需要认证的端点，用来判断网关是否还能服务：

```bash
curl https://hypit.ai/healthz
```

```json
{
  "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_*` 是运维计数
器，可能随时变化。它适合做负载均衡探针或状态页，不要拿它做每次请求前的预检。