ДокументацияВведение
Коды ошибок

Интеграция

Коды ошибок

Структура ошибок и обработка

Любая ошибка PayBot возвращается единым JSON-объектом с полем error, внутри которого — type, category, code, message и, для ошибок ввода, param. Ветвите обработку по category (главный переключатель UX), логику — по code, а request_id показывайте в поддержку. Так один обработчик покрывает все ошибки API, включая непойманные исключения.

Все ошибки, вплоть до внутренних 500, проходят через единый обработчик и имеют одинаковую форму — вам не нужно разбирать разные форматы для разных endpoint'ов.

Как устроено тело ошибки

Ошибка приходит в конверте {"error": {...}}.

json
Скачать
{
  "error": {
    "type": "validation_error",
    "code": "invalid_parameter",
    "message": "expires_at must be in the future",
    "request_id": "req_8f3c9a12b7d4",
    "param": "expires_at",
    "category": "validation"
  }
}

Поля объекта error:

ПолеЧто этоКак использовать
typeкласс ошибки: authentication_error, api_error, validation_error, rate_limit_error, proxy_error (встречаются также authorization_error, not_found, not_implemented)выбор канала показа
codeмашинный кодветвление логики, ключ для i18n
messageчеловекочитаемый текст (часть уже на русском и готова к показу)fallback-текст
request_idставится автоматически«Сообщите поддержке ID: …»
paramимя поля — только для validationподсветка конкретного инпута
categoryauth, rate_limit, validation, payment, temporary, permanentглавный переключатель UX

Если category не задана явно, она выводится из type: authentication_error → auth, rate_limit_error → rate_limit, validation_error → validation, proxy_error → temporary, api_error → permanent.

Как показывать ошибку по category

category — это и есть решение, что показать пользователю. Одна ветка на категорию покрывает подавляющее большинство случаев.

categoryСмыслЧто делать в UI
authтокен или ключ невалиден/отозванразлогинить → экран входа. Не показывать красный тост
validationплохой вводinline-подсказка под полем param; если param нет — над формой
rate_limitслишком часто или исчерпан лимит плананейтральный тост «повторите». Для plan_limit_exceeded — апселл, а не ошибка
temporaryKaspi или сеть тупят«Попробуйте ещё раз» + кнопка ретрая. Не пугать пользователя
permanentретраем не исправитсяявная ошибка + request_id
paymentобъявлена в схеме, но в коде не проставляетсяобрабатывать как permanent

HTTP-статусы

HTTP-код грубо совпадает с type, но окончательное решение принимайте по category и code, а не по статусу.

  • 400 — ошибка ввода (validation_error).
  • 401 — не авторизован: невалидный ключ, истёкший или отозванный токен.
  • 403 — доступ запрещён (authorization_error), например действие заблокировано под импersonation.
  • 404 — объект не найден.
  • 409 — конфликт состояния (идемпотентность, неизменяемая ссылка).
  • 429 — превышен лимит запросов или лимит плана.
  • 500 — внутренняя ошибка.
  • 501 — функция отключена (например загрузка логотипа).
  • 502 / 503 — временные проблемы на стороне Kaspi или сессии.

Коды, которые обязан знать фронт

Ниже — коды с готовой реакцией интерфейса.

HTTPcodeРеакция
401invalid_api_key, invalid_token, token_expired, token_revokedразлогин / переввод ключа
401impersonation_session_expiredвыход из режима партнёрского входа
403impersonation_blocked_actionзаранее дизейблить кнопку; текст уже на русском
403not_owner404-подобный экран
400idempotency_key_required (param: Idempotency-Key)баг фронта — не показывать пользователю
409idempotency_conflict«запрос уже выполняется» — не дублировать
400unknown_eventподсветить чекбоксы событий вебхука
400bad_color / bad_email (param)inline в форме брендинга
400invalid_parameter (param)inline под полем
404payment_link_not_found, merchant_not_found, client_not_foundпустое состояние
409payment_link_immutable, payment_link_not_active, payment_link_already_paidдизейблить действие по status заранее
429rate_limit_exceededбэкофф по X-RateLimit-Reset
429plan_limit_exceededапселл (текст уже на русском)
429payment_in_progress«подождите 30 секунд»
409trial_already_usedскрыть кнопку триала
501logo_upload_disabledfallback на ввод URL
502kaspi_error (category: temporary)кнопка «Обновить QR»
503session_expired (proxy_error)баннер «переподключите Kaspi» во всём кабинете
503payment_unavailable, qr_creation_failed, qr_failed«оплата временно недоступна»
500internal_errorобщий экран + request_id

Особый случай — 503 session_expired: он возвращается на защищённых API-key endpoint'ах, когда сессия Kaspi мертва, а ключ не в тестовом режиме. Это не «ошибка запроса», а сигнал показать баннер переподключения Kaspi по всему кабинету.

Как обрабатывать ошибки на клиенте

Один обработчик разбирает конверт и маршрутизирует по category, а частные code уточняют поведение.

python
Скачать
import requests

class PayBotError(Exception):
    def __init__(self, err: dict):
        self.type = err.get("type")
        self.code = err.get("code")
        self.message = err.get("message")
        self.category = err.get("category")
        self.param = err.get("param")
        self.request_id = err.get("request_id")
        super().__init__(self.message)

def call(method: str, url: str, **kwargs) -> dict:
    resp = requests.request(method, url, **kwargs)
    if resp.status_code >= 400:
        raise PayBotError(resp.json().get("error", {}))
    return resp.json()

def handle(err: PayBotError):
    if err.category == "auth":
        logout()  # красный тост не показываем
    elif err.category == "validation":
        show_field_error(err.param, err.message)  # или над формой, если param пуст
    elif err.category == "rate_limit":
        if err.code == "plan_limit_exceeded":
            show_upsell()
        else:
            toast(err.message)  # нейтрально, «повторите»
    elif err.category == "temporary":
        show_retry(err.message)  # кнопка «Попробовать снова»
    else:  # permanent и payment
        show_error(f"{err.message}. ID: {err.request_id}")

Ключевые принципы: auth не показывают тостом (это разлогин), validation крепят к полю по param, temporary дают ретрайнуть, а permanent показывают вместе с request_id для поддержки.

FAQ

По какому полю ветвить обработку — type или category?

По category — это осознанный UX-переключатель на бэке. type полезен для грубого канала показа, а code — для точечной логики (например plan_limit_exceeded → апселл). category покрывает случаи, которые ещё не имеют отдельного кода.

Что делать с request_id?

Показывайте его пользователю при permanent/internal_error («Сообщите поддержке ID: …») и логируйте на своей стороне. Тот же request_id виден в GET /me/logs, что связывает ошибку с конкретным запросом.

Почему при validation иногда нет param?

param заполняется только когда ошибка привязана к конкретному полю. Если его нет — покажите message над формой целиком, а не под инпутом.

Категория payment — как её обрабатывать?

Как permanent. Значение payment объявлено в схеме ErrorDetail, но в коде не проставляется, поэтому не закладывайте на неё отдельную ветку UX.

Смежные разделы