Для AI-агентов: markdown этой страницы — /docs-content/errors/handling.md индекс документации — /llms.txt
Повторы и обработка ошибок в коде
Сводка стратегий повтора по кодам временных отказов и готовые обработчики ответа на JavaScript, Python и PHP.
Сводная таблица всех кодов API Вайбкод — Коды ошибок.
Повторы и паузы
Сводка по кодам, которые сигнализируют о временной проблеме и подразумевают повтор:
| Код | HTTP | Стратегия повтора |
|---|---|---|
RATE_LIMITED, QUEUE_OVERFLOW, QUEUE_TIMEOUT |
429 | Повтор через Retry-After, дальше — с растущей паузой и случайной добавкой к ней |
LARGE_BODY_BACKEND_BUSY |
429 | Повтор через Retry-After со случайной добавкой к паузе. Запрос не выполнялся и состояние не менял — повтор безопасен и для записи |
OPERATION_TIME_LIMIT |
429 | Повтор строго через Retry-After: раньше этого срока тот же вызов не пройдёт. scope: "apiKey" — пауза только на вызывающем ключе |
TIMEOUT_QUARANTINE |
429 | Повтор через Retry-After со случайной добавкой к паузе, и не сокращая интервал: досрочный повтор продлевает паузу. scope: "portal" — пауза общая для всех ключей портала |
ERROR_LOOP_DETECTED |
429 | Повтор через Retry-After, поля retryAfter в теле нет. Сначала разберите причину: код означает серию одинаковых отказов на одном ключе. Каждый N-й повтор платформа пропускает как пробу восстановления |
BITRIX_TIMEOUT |
503 | Более длинная пауза перед повтором, чем при 429. Для записи — сначала перечитайте сущность: изменение могло уже примениться |
BITRIX_UNAVAILABLE |
502 | Это 5xx самого Битрикс24 или сетевая проблема между Вайбкод и порталом, а не перегрузка очереди: заголовка Retry-After в ответе нет, повторяйте с экспоненциальной задержкой. Для записи — сначала проверьте, не применилась ли она |
POOL_EXHAUSTED, DB_TRANSIENT, SERVICE_UNAVAILABLE |
503 | Повтор через retryAfter (он же заголовок Retry-After) — несколько секунд. У DB_TRANSIENT транзакция откачена целиком, поэтому повтор безопасен и для записи. У SERVICE_UNAVAILABLE исход мог остаться неизвестным — для не-идемпотентных операций сверьте состояние сущности перед повтором |
INTERNAL_ERROR |
500 | Повторить запрос. Стабильно воспроизводимая ошибка повтором не лечится — отправьте обращение с временем запроса и заголовком X-Request-Id |
Таблица покрывает отказы, которые приходят на любом эндпоинте. Остальные временные состояния — установку приложения через модуль-коннектор, ожидание обновления Битрикс24, заморозку ключа на время удаления аккаунта — описывает строка кода в сводной таблице: там сказано, меняет ли что-нибудь повтор.
Обработка ошибок в коде
Обработчики ниже ограничивают число попыток. Ограничение обязательно: отказ, который держится дольше вашего терпения, при бесконечном повторе превращается в серию одинаковых запросов, и платформа закрывает её кодом ERROR_LOOP_DETECTED.
JavaScript
const MAX_ATTEMPTS = 5;
async function vibeRequest(url, options = {}, attempt = 1) {
const response = await fetch(url, {
...options,
headers: {
'X-Api-Key': process.env.VIBE_API_KEY,
'Content-Type': 'application/json',
...options.headers,
},
});
const data = await response.json();
if (!data.success) {
const { code, message, retryAfter } = data.error;
const canRetry = attempt < MAX_ATTEMPTS;
switch (code) {
case 'RATE_LIMITED':
case 'QUEUE_TIMEOUT': {
if (!canRetry) throw new Error(`${code}: отказ держится ${MAX_ATTEMPTS} попыток подряд`);
const wait = retryAfter ?? Number(response.headers.get('Retry-After') ?? 1);
await new Promise(r => setTimeout(r, wait * 1000));
return vibeRequest(url, options, attempt + 1);
}
case 'BITRIX_UNAVAILABLE':
if (!canRetry) throw new Error(`${code}: отказ держится ${MAX_ATTEMPTS} попыток подряд`);
await new Promise(r => setTimeout(r, 5000));
return vibeRequest(url, options, attempt + 1);
case 'MISSING_API_KEY':
case 'INVALID_API_KEY':
throw new Error('Проверьте API-ключ');
default:
throw new Error(`${code}: ${message}`);
}
}
return data;
}
Python
import os
import time
import requests
MAX_ATTEMPTS = 5
def vibe_request(url, method="GET", json_data=None, attempt=1):
headers = {
"X-Api-Key": os.environ["VIBE_API_KEY"],
"Content-Type": "application/json",
}
response = requests.request(method, url, headers=headers, json=json_data)
data = response.json()
if not data.get("success"):
err = data.get("error", {})
code = err.get("code")
message = err.get("message")
retry_after = err.get("retryAfter") or int(response.headers.get("Retry-After", 1))
can_retry = attempt < MAX_ATTEMPTS
if code in ("RATE_LIMITED", "QUEUE_TIMEOUT"):
if not can_retry:
raise Exception(f"{code}: отказ держится {MAX_ATTEMPTS} попыток подряд")
time.sleep(retry_after)
return vibe_request(url, method, json_data, attempt + 1)
if code == "BITRIX_UNAVAILABLE":
if not can_retry:
raise Exception(f"{code}: отказ держится {MAX_ATTEMPTS} попыток подряд")
time.sleep(5)
return vibe_request(url, method, json_data, attempt + 1)
raise Exception(f"{code}: {message}")
return data
PHP
const MAX_ATTEMPTS = 5;
function vibeRequest(string $url, string $method = 'GET', ?array $data = null, int $attempt = 1): array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => [
'X-Api-Key: ' . getenv('VIBE_API_KEY'),
'Content-Type: application/json',
],
]);
if ($data !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
$body = json_decode(curl_exec($ch), true);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === null) {
throw new Exception("HTTP error: $httpCode");
}
if (empty($body['success'])) {
$errCode = $body['error']['code'] ?? 'UNKNOWN';
$errMsg = $body['error']['message'] ?? 'Unknown error';
$retryAfter = $body['error']['retryAfter'] ?? 1;
$canRetry = $attempt < MAX_ATTEMPTS;
if (in_array($errCode, ['RATE_LIMITED', 'QUEUE_TIMEOUT'], true)) {
if (!$canRetry) {
throw new Exception("$errCode: отказ держится " . MAX_ATTEMPTS . ' попыток подряд');
}
sleep((int) $retryAfter);
return vibeRequest($url, $method, $data, $attempt + 1);
}
if ($errCode === 'BITRIX_UNAVAILABLE') {
if (!$canRetry) {
throw new Exception("$errCode: отказ держится " . MAX_ATTEMPTS . ' попыток подряд');
}
sleep(5);
return vibeRequest($url, $method, $data, $attempt + 1);
}
throw new Exception("$errCode: $errMsg");
}
return $body;
}