错误码与重试
ttttt.ai 网关使用统一的错误信封,无论上游返回什么格式,对外都规整成下面这种结构:
{
"error": {
"message": "Human readable description",
"type": "invalid_request_error",
"code": 400
}
}type 是机器可读的语义码,code 与 HTTP 状态码一致,message 是给人看的解释(可能包含上游原文)。
HTTP 状态码总表
| HTTP | type | 含义 | 重试 |
|---|---|---|---|
400 | invalid_request_error | 请求体不合法(缺字段、类型错、参数越界) | ❌ 不要重试 |
401 | unauthorized | 密钥无效 / 已停用 / 已过期 | ❌ |
402 | insufficient_balance | 组织余额不足 / 订阅额度耗尽且未开溢出 | ❌(先充值/调订阅) |
403 | forbidden | 密钥 scope 拒绝 / 模型未开放 / 组织被冻结 | ❌ |
404 | not_found | 模型 ID 不存在 / 路径写错 | ❌ |
409 | conflict | 资源冲突(罕见,主要在控制台 API) | ❌ |
429 | rate_limit_exceeded | 触发了密钥 RPM/TPM 或上游限速 | ✅ 指数退避 |
499 | client_closed | 客户端中断(流式专用) | 业务决定 |
500 | server_error | 网关内部错误 | ✅ 短退避 |
502 | bad_gateway | 上游不可达 | ✅ 短退避 |
503 | service_unavailable | 上游或网关过载 | ✅ 短退避 |
504 | gateway_timeout | 上游超时 | ✅ 短退避 |
401 unauthorized
最常见原因 + 排查:
| 现象 | 检查 |
|---|---|
Invalid API key | 控制台密钥列表里这把 Key 是否还存在 / 状态是否 active |
Key expired | 是否到了 expiresAt;过期密钥不会自动续 |
Missing API key | 请求是不是真的发出了 Authorization / x-api-key header(curl 加 -v 看实际发出的 header) |
Organization deactivated | 组织是否进入冻结期;联系工单 |
402 insufficient_balance
{
"error": {
"message": "Insufficient balance: required $0.0145, available $0.0021",
"type": "insufficient_balance",
"code": 402
}
}含义:本次预估费用 + 安全冗余 > 当前可用余额(三桶合计)。
处理:
- 充值:控制台 → 账单 → 充值
- 长期可通过 低余额告警 提前预警,详见 余额告警
403 forbidden
{
"error": {
"message": "Model 'claude-opus-4-7' is not allowed for this API key",
"type": "forbidden",
"code": 403
}
}排查:
- 该模型当前是否在公开列表里(模型清单)
- 密钥
modelScope是否限制了模型
429 rate_limit_exceeded
{
"error": {
"message": "Rate limit exceeded: 60 requests per minute",
"type": "rate_limit_exceeded",
"code": 429
}
}响应可能附带:
Retry-After: 12客户端应当遵循
Retry-After(秒);没有该 header 时按指数退避(1s → 2s → 4s → 8s,最多 5 次)。
被限速的常见原因:
- 密钥本身设置了
rateLimitRpm/rateLimitTpm - 上游供应商对你的请求模式触发了限速(突发并发太高)
5xx 系列
500 / 502 / 503 / 504 都属于”应当短期重试”的临时错误:
// 推荐重试策略
async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 3): Promise<T> {
let attempt = 0;
while (true) {
try {
return await fn();
} catch (e: any) {
attempt++;
const status = e.status ?? e.response?.status;
const retriable = status === 429 || (status >= 500 && status < 600);
if (!retriable || attempt >= maxAttempts) throw e;
const delayMs = Math.min(2 ** attempt * 500, 8000) + Math.random() * 200;
await new Promise(r => setTimeout(r, delayMs));
}
}
}平台自身已经做了一层渠道降级——上游 5xx 时网关会切换到同型号的备用渠道再重试一次,所以如果你拿到了 5xx,意味着所有备用都失败了。建议前端重试 ≤ 3 次,再多就该报错让人介入。
错误响应里的 Request ID
每个错误响应同样带 X-Request-ID: req-... header,把它存到你的日志里,提工单时附上 ID 运营可以一秒定位。
curl -i https://api.ttttt.ai/v1/chat/completions ...
# < HTTP/1.1 502 Bad Gateway
# < X-Request-ID: req-018ff3a2-9bca-7f29-bd0a-1c4b3d8e0c8e失败请求的计费
| 状态码 | 是否扣费 |
|---|---|
2xx | 按真实 usage 扣 |
4xx(客户端错误) | 不扣费 |
5xx(最终失败) | 不扣费 |
429(限速) | 不扣费 |
| 流式中断 | 按已估算的 output 部分扣 |
详见 计费模型 → 失败请求的计费。
与官方 SDK 的兼容
OpenAI / Anthropic SDK 的内置重试逻辑(max_retries=2 等)直接可用——它们会按 HTTP 状态码 + Retry-After 处理 429/5xx,与 ttttt.ai 网关行为一致。
不需要自己实现的话,把 SDK 的 max_retries 调到 2-3 即可:
client = OpenAI(api_key="owo-...", base_url="https://api.ttttt.ai/v1", max_retries=3)Last updated on