Все ошибки PayBot возвращаются единообразно: JSON с типом, кодом, человекочитаемым сообщением и
request_id для дебага.Формат
json
{
"error": {
"type": "validation_error",
"code": "amount_too_small",
"message": "Минимальная сумма платежа — 100 тенге",
"request_id": "req_abc123def456",
"param": "amount",
"category": "validation"
}
}| Поле | Описание |
|---|---|
type | Группа: authentication_error, api_error, validation_error, rate_limit_error, proxy_error |
code | Машиночитаемый код для switch/case |
message | Текст для логов и (опционально) пользователя |
request_id | Уникальный ID запроса — присылайте в поддержку для расследования |
param | Поле, вызвавшее ошибку (только для validation_error) |
category | auth, rate_limit, validation, payment, temporary, permanent |
request_id также возвращается в HTTP-заголовке X-Request-Id на каждом ответе, даже успешном.
Категории и стратегия retry
| Категория | Retry | Стратегия |
|---|---|---|
auth | нет | Проверить ключи, не повторять |
validation | нет | Исправить запрос на стороне клиента |
rate_limit | да | Exponential backoff, см. Rate Limits |
temporary | да | Backoff с тем же Idempotency-Key |
payment | условно | Кли. ошибка Kaspi — обычно не ретраить |
permanent | нет | Объект уже в финальном состоянии |
Authentication errors (401, 403)
| Code | HTTP | Описание |
|---|---|---|
missing_api_key | 401 | Не передан X-API-Key |
invalid_api_key | 401 | Ключ не существует или отозван |
invalid_token | 401 | JWT просрочен или плохо подписан |
inactive_account | 403 | Аккаунт заблокирован |
plan_limit_exceeded | 403 | Исчерпан лимит платежей по тарифу |
feature_not_in_plan | 403 | Функция недоступна на вашем тарифе |
Validation errors (400)
| Code | Описание |
|---|---|
amount_too_small | Сумма < 100 тенге |
amount_too_large | Сумма > 1 000 000 тенге |
invalid_phone | Телефон не в формате 77XXXXXXXXX |
invalid_email | Email не прошёл базовую валидацию |
invalid_url | URL не HTTPS или некорректен |
invalid_cursor | Cursor подделан или повреждён |
invalid_limit | limit <= 0 или > 100 |
idempotency_key_required | Обязательный Idempotency-Key не передан |
idempotency_conflict | Тот же ключ с другим телом запроса |
metadata_too_large | Metadata > 4 KB |
description_too_long | Описание > 500 символов |
unsupported_event_type | Неизвестный тип в фильтре ?type= |
Payment errors (402, 422)
| Code | HTTP | Описание |
|---|---|---|
kaspi_insufficient_funds | 402 | На счету клиента недостаточно денег |
kaspi_user_blocked | 422 | Клиент заблокирован в Kaspi |
kaspi_limit_exceeded | 422 | Превышен дневной лимит клиента |
operation_not_found | 404 | Нет такой operation_id |
operation_not_refundable | 422 | Прошло 90 дней или статус не Processed |
amount_too_large_for_refund | 422 | Сумма возврата > доступной |
phone_not_in_kaspi | 422 | У номера нет Kaspi-аккаунта |
link_not_active | 409 | Платёжная ссылка не в статусе active |
link_expired | 410 | Срок действия ссылки истёк |
Rate limit errors (429)
| Code | Описание |
|---|---|
rate_limit_exceeded | Превышен лимит запросов |
concurrent_request_limit | Слишком много параллельных запросов |
creation_throttled | Слишком частое создание QR/инвойса с одного IP |
Подробнее: Rate Limits.
Server errors (5xx)
| Code | HTTP | Описание | Retry |
|---|---|---|---|
internal_error | 500 | Внутренняя ошибка PayBot | да |
kaspi_unavailable | 502 | Kaspi не отвечает | да, с backoff |
kaspi_timeout | 504 | Kaspi не ответил за timeout | да, с тем же Idempotency-Key |
database_unavailable | 503 | Временные проблемы БД | да |
circuit_breaker_open | 503 | PayBot временно отключил вызовы Kaspi | да, через 30–60 сек |
Обработка в коде
Python
python
import requests
def safe_create(amount, idem_key):
try:
r = requests.post(
"https://api.paybot.kz/v2/qr",
headers={"X-API-Key": "kp_live_xxx", "Idempotency-Key": idem_key},
json={"amount": amount},
timeout=10,
)
except requests.Timeout:
return retry_with_backoff(amount, idem_key)
if r.ok:
return r.json()
err = r.json().get("error", {})
code = err.get("code")
rid = err.get("request_id")
if err.get("category") == "temporary":
return retry_with_backoff(amount, idem_key)
if code == "rate_limit_exceeded":
retry_after = int(r.headers.get("Retry-After", 5))
time.sleep(retry_after)
return safe_create(amount, idem_key)
if code in ("invalid_api_key", "inactive_account"):
raise FatalError(f"check credentials, request_id={rid}")
raise APIError(f"{code}: {err.get('message')} (request_id={rid})")Node.js
javascript
async function safeCreate(amount, idemKey) {
const res = await fetch("https://api.paybot.kz/v2/qr", {
method: "POST",
headers: {
"X-API-Key": "kp_live_xxx",
"Idempotency-Key": idemKey,
"Content-Type": "application/json",
},
body: JSON.stringify({ amount }),
});
if (res.ok) return res.json();
const { error } = await res.json();
if (error.category === "temporary") {
return retryWithBackoff(amount, idemKey);
}
if (error.code === "rate_limit_exceeded") {
const wait = Number(res.headers.get("retry-after") || "5") * 1000;
await new Promise(r => setTimeout(r, wait));
return safeCreate(amount, idemKey);
}
throw new Error(`${error.code}: ${error.message} (rid=${error.request_id})`);
}Запрос в поддержку
При обращении в поддержку прикладывайте:
- •
request_idиз заголовкаX-Request-Idили поляerror.request_id - •HTTP-код ответа
- •Время запроса (UTC)
- •Тип API-ключа (
live/test), последние 4 символа ключа
Это позволяет за 1–2 минуты найти запрос в логах.
Что дальше
- •Rate Limits — стратегии backoff
- •Идемпотентность —
idempotency_conflictи связанные - •Webhooks — отдельная семантика ошибок доставки