Ошибки и повторы
Все эндпоинты, кроме чата, отвечают конвертом с полем error.kind. Чат говорит на языке ошибок OpenAI. Ниже — что означает каждый kind и что с ним делать.
Конверт
Успех и ошибка приходят в одинаковой оболочке: success, code, data, error. У ошибки всегда есть kind и message, а иногда — retry_after_sec с рекомендованной паузой в секундах.
{
"success": false,
"code": 429,
"data": null,
"error": {
"kind": "rate_limited",
"message": "rate limit exceeded (rpm)",
"retry_after_sec": 12
}
}{
"error": {
"message": "no healthy text provider",
"type": "invalid_request_error"
}
}Виды ошибок
| kind | HTTP | Что произошло | Повторять? |
|---|---|---|---|
| invalid_request | 400 | Тело запроса не прошло валидацию: нет prompt или messages, кривой aspect_ratio, неизвестный provider. | Нет — исправь запрос |
| unauthorized | 401 | Ключ отсутствует, отозван или неверен. | Нет — проверь ключ |
| forbidden | 403 | Доступ к ресурсу запрещён, например протухшая подпись у ссылки на файл. | Нет |
| not_found | 404 | Объект не найден — например, чужой или удалённый job. | Нет |
| rate_limited | 429 | Упёрся в rpm или в конкурентность тарифа. | Да — после паузы |
| quota_exceeded | 429 | Исчерпана суточная или пожизненная квота тарифа. | Нет — до сброса квоты или смены тарифа |
| no_healthy_provider | 503 | Ни один подходящий провайдер сейчас не доступен. | Да — с бэкоффом |
| provider_error | 502 | Все перебранные провайдеры вернули ошибку. | Да — с бэкоффом |
| timeout | 504 | Апстрим не ответил в отведённое время. | Да — с бэкоффом |
| internal_error | 500 | Внутренняя ошибка шлюза. | Да — с бэкоффом |
Что уже делает шлюз
Один вызов /v1 — это одна попытка с твоей стороны, но не обязательно одна попытка внутри. Шлюз сам перебирает провайдеров по пулу с учётом здоровья и брейкера, и в результате job поле attempts показывает, сколько их понадобилось. За неуспешные внутренние попытки ты не платишь.
Что стоит делать у себя
- Уважай retry_after_sec, когда он пришёл: это не оценка, а расчёт шлюза.
- На 429 из-за rpm — экспоненциальный бэкофф с джиттером; на quota_exceeded повтор бесполезен.
- Не ретрай 400, 401, 403 и 404: повтор с тем же телом даст тот же ответ.
- Для генерации изображений повторяй опрос job, а не сам POST — иначе создашь дубль задачи и потратишь квоту дважды.
Пример обработки
import time, httpx
def call_with_retry(request, attempts=4):
delay = 1.0
for attempt in range(attempts):
response = request()
if response.status_code < 400:
return response
body = response.json()
kind = (body.get("error") or {}).get("kind")
# Client mistakes and auth failures never get better on retry.
if response.status_code in (400, 401, 403, 404):
response.raise_for_status()
# The gateway tells you how long to wait when it knows.
wait = (body.get("error") or {}).get("retry_after_sec") or delay
if kind == "quota_exceeded":
raise RuntimeError("quota exhausted — upgrade the plan or wait for the reset")
time.sleep(wait)
delay *= 2
response.raise_for_status()