开始
错误码与重试
出错时先看 HTTP 状态码:429、500、502、503 可以退避重试,400、401、403、404、413 是请求或账户的问题,重试不会成功。余额不足返回 403,不是 OpenAI 的 429。
请求出错时会返回 HTTP 错误状态码和一个 JSON 错误体。判断要不要重试,看状态码就够了:429、500、502、503 可以等一会儿再试;400、401、403、404、413 是请求本身或账户设置的问题,原样重试不会成功,只会浪费时间。
错误体格式
{
"error": {
"code": "",
"message": "无效的令牌 (request id: 2026111202521796606900082...)",
"type": "new_api_error"
}
}| 字段 | 说明 |
|---|---|
message | 可读的错误描述,末尾带 request id |
type | 错误类别 |
code | 错误码,例如 insufficient_user_quota、model_not_found;很多错误这里是空字符串 |
code 经常为空,程序里请以 HTTP 状态码为准,code 只作补充判断。Claude Messages 接口(/v1/messages)在转发阶段出错时,错误体是 Anthropic 的格式,即 {"type": "error", "error": {...}};认证、余额这类请求前的检查失败,所有接口都用上面的格式。
找客服排查时,把 message 里括号中的 request id 一起发过来(客服微信 JsnonoChat),我们能据此定位到这一次请求。建议在日志里完整记录错误响应。
状态码对照
| 状态码 | 含义 | 常见原因 | 重试 |
|---|---|---|---|
| 400 | 请求有误 | JSON 格式不对、缺少必填参数、参数取值超出范围、模型不支持某个参数、输入超过模型的上下文长度 | 不重试,先修正请求 |
| 401 | 认证失败 | Key 填错、已删除、已禁用、已过期,或这把 Key 的额度上限用完 | 不重试 |
| 403 | 没有权限 | 账户余额不足(insufficient_user_quota)、IP 不在 Key 的白名单(access_denied)、模型不在 Key 的模型白名单、Key 的分组不可用、账户被禁用 | 不重试 |
| 404 | 不存在 | 路径写错(比如漏了 /v1)、请求的资源不存在 | 不重试 |
| 413 | 请求体太大 | 一次传入的图片、音频或文本过大 | 不重试,压缩或拆分后再发 |
| 429 | 请求过多 | 触发了本站或模型服务的限流 | 退避后重试 |
| 500 | 服务内部错误 | 服务端处理请求时出现异常 | 退避后少量重试 |
| 502 | 网关错误 | 模型服务返回了异常响应,或连接中断 | 退避后重试 |
| 503 | 暂时不可用 | 模型服务繁忙;或者 Key 所在分组没有这个模型的可用渠道(model_not_found) | 退避后重试;带 model_not_found 时换分组 |
余额不足在这里返回 403,错误码 insufficient_user_quota。OpenAI 官方接口遇到额度不足返回的是 429。如果你的代码沿用 OpenAI 的判断逻辑,请把 403 单独处理:提醒充值或切换账户,不要放进重试队列。
503 并提示 model_not_found
这表示当前 Key 所在的分组没有这个模型的可用线路,常见于模型名拼错或者分组里本来就没有这个模型。处理顺序:
重试策略
可以重试的错误(429、500、502、503)用指数退避加随机抖动:第 1 次等 1 秒左右,之后每次翻倍,设一个上限,最多重试 3 到 5 次。抖动能避免大量客户端在同一时刻一起重试。
OpenAI 官方 SDK 自带重试,遇到 429、5xx 和连接错误会自动退避重试,重试次数和超时都可以设置:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://open.tutujin.com/v1",
api_key=os.environ["TUTUJIN_API_KEY"],
max_retries=4, # 默认 2 次
timeout=60.0, # 单次请求超时,秒
)
# 单个请求临时调整
resp = client.with_options(max_retries=6, timeout=120.0).chat.completions.create(
model="gpt-5.4-mini",
messages=[{"role": "user", "content": "你好"}],
)import OpenAI from 'openai'
const client = new OpenAI({
baseURL: 'https://open.tutujin.com/v1',
apiKey: process.env.TUTUJIN_API_KEY,
maxRetries: 4, // 默认 2 次
timeout: 60_000, // 单次请求超时,毫秒
})
// 单个请求临时调整
const resp = await client.chat.completions.create(
{
model: 'gpt-5.4-mini',
messages: [{ role: 'user', content: '你好' }],
},
{ maxRetries: 6, timeout: 120_000 },
)不用 SDK、直接发 HTTP 请求时,可以这样写:
import os
import random
import time
import requests
RETRYABLE = {429, 500, 502, 503}
def post_with_retry(path, payload, max_attempts=5):
url = "https://open.tutujin.com/v1" + path
headers = {"Authorization": f"Bearer {os.environ['TUTUJIN_API_KEY']}"}
for attempt in range(max_attempts):
r = requests.post(url, json=payload, headers=headers, timeout=60)
if r.status_code not in RETRYABLE or attempt == max_attempts - 1:
r.raise_for_status() # 400/401/403/404/413 直接抛出
return r.json()
retry_after = r.headers.get("Retry-After", "")
delay = float(retry_after) if retry_after.isdigit() else min(30, 2**attempt)
time.sleep(delay + random.uniform(0, 1))const RETRYABLE = new Set([429, 500, 502, 503])
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
async function postWithRetry(path, payload, maxAttempts = 5) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(`https://open.tutujin.com/v1${path}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TUTUJIN_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(60_000),
})
if (res.ok) return res.json()
if (!RETRYABLE.has(res.status) || attempt === maxAttempts - 1) {
throw new Error(`${res.status} ${await res.text()}`) // 不可重试,或已用完次数
}
const retryAfter = Number(res.headers.get('retry-after'))
const delay = retryAfter > 0 ? retryAfter * 1000 : Math.min(30_000, 1000 * 2 ** attempt)
await sleep(delay + Math.random() * 1000)
}
}流式请求出错
流式请求在开始返回数据之前出错,和普通请求一样返回错误状态码。如果数据已经开始返回,HTTP 状态码已经是 200,出错表现为流提前结束、没有收到最后的 data: [DONE]。客户端要把「没收到 [DONE] 就断开」当作失败处理,已经输出的半截内容按需丢弃或提示用户重试。
排查清单
- 401:打印请求头确认 Key 完整、没有多余空格或换行,在控制台确认 Key 未过期、未禁用、额度上限没用完。
- 403:看
message判断是余额、IP 白名单、模型白名单还是分组问题,对应去控制台调整。 - 404:确认地址是
https://open.tutujin.com/v1开头的完整路径。 - 429:降低并发,或者把请求分散到多把 Key,见限流说明。
- 一直 5xx:记下 request id,联系客服。
没找到答案?加客服微信 JsnonoChat,发送报错信息里的 request id,我们帮你排查。