# Каталог ошибок ApiPay

> Источник: https://apipay.kz/errors · Обновлено: 2026-07-22 · apipay.kz
> Постоянные якоря: doc_url в ответе API ведёт на https://apipay.kz/errors#{error_code}.

Ошибки приходят в нескольких формах — не путайте их между собой:

- **HTTP-статус** (`400`, `401`, `429` …) — общий класс ошибки, присутствует всегда.
- **Поле `error`** в теле ответа — конкретная причина синхронной ошибки. Историческая
  особенность бэкенда: часть значений — машинные коды в snake_case
  (`organization_required`, `kaspi_session_not_configured`), а часть — английские фразы
  целиком (`Organization not found or not verified`, `Invoice cannot be cancelled`).
  Поэтому форматы и различаются. **Не сравнивайте текст `error` в коде.**
- **Поле `error_message`** — человекочитаемый текст асинхронной ошибки Kaspi (счёт создан
  со статусом `processing` и позже перешёл в `error`). Фиксированных кодов у Kaspi нет.
- **Поле `error_code`** (новое) — стабильный snake_case-код из фиксированного каталога.
  Стройте switch-логику по нему, а не по тексту `error`/`error_message`.

В колонке «Код» ниже значения сгруппированы по тому, где именно они появляются.

## HTTP-статусы (общие для всех эндпоинтов)

| Код | HTTP | Что это и что делать |
|---|---|---|
| `400` (#400) | 400 | Bad Request — некорректный запрос или недопустимое состояние. Точная причина — в поле message или error |
| `401` (#401) | 401 | Unauthorized — API-ключ отсутствует, неверен, истёк или не привязан к организации; либо аккаунт деактивирован |
| `403` (#403) | 403 | Forbidden — организация заморожена (suspended) или не верифицирована для рабочего режима |
| `tariff_inactive` (#tariff_inactive) | 403 | Нет действующей подписки на ApiPay — оплатите тариф в кабинете. Закрывает все платные операции: создание, отмену и возврат счёта, чеки, изменяющий каталог, создание, изменение и возобновление подписок, проверку номера клиента. Грейса нет — блокировка сразу после expires_at (это поле приходит в теле ответа; null, если тариф не оформлялся). Чтение (GET), оплата тарифа, настройки, подключение кассира, а также приостановка и отмена подписок продолжают работать. В песочнице тариф не требуется |
| `404` (#404) | 404 | Not Found — ресурс не найден или принадлежит другой организации |
| `422` (#422) | 422 | Validation Error — ошибка валидации полей; детали в объекте errors |
| `429` (#429) | 429 | Too Many Requests — превышен общий лимит Public API (200 запросов/мин на API-ключ); смотрите заголовок Retry-After. Отдельно: POST /clients/check ограничен 60 запросами/мин и 10 000 запросами/сутки на API-ключ, а POST /invoices/qr — 60 QR/мин на организацию (на этом 429 заголовка Retry-After нет) |
| `500` (#500) | 500 | Server Error — внутренняя ошибка сервера |
| `502` (#502) | 502 | Bad Gateway — ошибка на стороне Kaspi API |
| `503` (#503) | 503 | Service Unavailable — сессия Kaspi недействительна или истекла |

## Поле «error» при создании счёта (POST /invoices, /invoices/qr)

| Код | HTTP | Что это и что делать |
|---|---|---|
| `organization_required` (#organization_required) | 400 | Организация не подключена — создайте sandbox-организацию для тестов или подключите кассира Kaspi |
| `Organization not found or not verified` (#organization-not-found-or-not-verified) | 400 | Рабочий режим: организация не верифицирована. Дождитесь верификации или тестируйте в песочнице |
| `kaspi_session_not_configured` (#kaspi_session_not_configured) | 400 | Кассир Kaspi не подключён. Подключите его в кабинете (Настройки → Авторизация Kaspi) или через поддержку (WhatsApp +7 708 516 74 89) |
| `kaspi_session_invalid` (#kaspi_session_invalid) | 503 | Сессия кассира Kaspi истекла или сброшена. Переподключите кассира — запросите новый SMS-код |
| `connection_ambiguous` (#connection_ambiguous) | 422 | У организации несколько активных касс, основная не выбрана — передайте kaspi_connection_id |
| `sandbox_invoice_limit` (#sandbox_invoice_limit) | 400 | Достигнут лимит тестовых счетов (500 на организацию) — очистите песочницу в кабинете |
| `duplicate_idempotency_key` (#duplicate_idempotency_key) | 409 | Идемпотентность: активный счёт с таким external_order_id_idempotency уже существует — повторный POST /invoices с тем же ключом не создаёт дубликат (в ответе invoice_id и status существующего счёта). Перевыставление возможно, только если предыдущий счёт с этим external_order_id_idempotency находится в статусе expired, cancelled или error |

## Поле «error» только для QR-счёта (POST /invoices/qr)

| Код | HTTP | Что это и что делать |
|---|---|---|
| `qr_rate_limit` (#qr_rate_limit) | 429 | Слишком много QR-запросов для организации (лимит 60/мин) — подождите минуту. Заголовок Retry-After на этом 429 не возвращается (в отличие от общего лимита Public API 200/мин) |
| `qr_render_failed` (#qr_render_failed) | 500 | Не удалось сформировать изображение QR-кода — повторите запрос позже |
| `kaspi_error` (#kaspi_error) | 502 | Kaspi API вернул ошибку при создании QR-токена — повторите позже |

## Поле «error» при отмене и возврате

| Код | HTTP | Что это и что делать |
|---|---|---|
| `Invoice cannot be cancelled` (#invoice-cannot-be-cancelled) | 400 | Отменить можно только счёт в статусе pending или processing |
| `Invoice is not refundable` (#invoice-is-not-refundable) | 400 | Возврат возможен только по оплаченному счёту, ещё не возвращённому полностью |
| `Refund amount exceeds available amount` (#refund-amount-exceeds-available-amount) | 400 | Сумма возврата больше доступной — смотрите available_for_refund в GET /invoices/{id} |

## Поле «error» при работе с подписками

| Код | HTTP | Что это и что делать |
|---|---|---|
| `sandbox_subscription_limit` (#sandbox_subscription_limit) | 400 | Достигнут лимит тестовых подписок (10 на организацию) — очистите песочницу |
| `Organization not verified` (#organization-not-verified) | 403 | Подписки в рабочем режиме доступны только верифицированной организации |

## Асинхронные ошибки Kaspi: status=error / поле error_message (HTTP-кода нет)

| Код | HTTP | Что это и что делать |
|---|---|---|
| `status=error` (#status-error) | — | Счёт создан (201, статус processing), но Kaspi не смог его обработать — статус сменился на error. Причина текстом в поле error_message (GET /invoices/{id}). У Kaspi нет фиксированных кодов — текст приходит как есть |
| `error_message: номер не в Kaspi` (#error-message-nomer-ne-v-kaspi) | — | «Этот номер телефона не зарегистрирован в Kaspi. Укажите номер с установленным приложением Kaspi.» — у клиента нет приложения Kaspi; попросите другой номер |
| `error_message: сбой Kaspi` (#error-message-sboy-kaspi) | — | «Ошибка обработки платежа. Обратитесь в поддержку» или «Не удалось обработать счёт после нескольких попыток» — временный сбой Kaspi; повторите создание счёта позже |

## Поле error_code — стабильный машинный код (новое, рекомендуется)

| Код | HTTP | Что это и что делать |
|---|---|---|
| `error_code (новое поле)` (#error-code-novoe-pole) | — | Стабильный snake_case-код ошибки из фиксированного каталога (31 значение). Присутствует в JSON-ответах об ошибках и в webhook-объектах invoice (для status=error) и refund (для status=failed). Поля message/error сохранены без изменений. Определяйте тип ошибки по error_code, текст — для показа пользователю. У каждого кода ниже указана «Доставка» — приходит ли он асинхронно (в webhook) или синхронно (HTTP-ответ с кодом) |
| `network_unavailable` (#network_unavailable) | — | Сервис временно недоступен (сбой сети/Kaspi). Можно повторить позже. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
| `session_transient` (#session_transient) | — | Временные проблемы авторизации Kaspi. Можно повторить попытку. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
| `client_not_found` (#client_not_found) | — | Номер телефона не зарегистрирован в Kaspi. Не повторяемая — попросите другой номер. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
| `kaspi_throttled` (#kaspi_throttled) | — | Kaspi ограничил частоту запросов. Повторите через 2–3 минуты. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
| `refund_window_expired` (#refund_window_expired) | — | Срок возврата истёк или возврат уже сделан (часто для refund status=failed). Доставка: async — приходит в invoice.refunded (refund.error_code) |
| `invoice_already_paid` (#invoice_already_paid) | — | Счёт уже оплачен. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
| `invoice_already_cancelled` (#invoice_already_cancelled) | — | Счёт уже отменён. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
| `invoice_not_found_in_kaspi` (#invoice_not_found_in_kaspi) | — | Счёт не найден в Kaspi. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
| `organization_not_configured` (#organization_not_configured) | — | Организация не настроена (нет рабочей привязки Kaspi). Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
| `unknown_error` (#unknown_error) | — | Непредвиденная ошибка обработки — обратитесь в поддержку. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
| `qr_render_failed` (#qr_render_failed-2) | 500 | Не удалось сформировать изображение QR-кода — повторите запрос. Доставка: sync HTTP 500 + async (webhook invoice.status_changed, status=error) |
| `kaspi_session_invalid` (#kaspi_session_invalid-2) | 503 | Сессия кассира Kaspi истекла или сброшена — переподключите кассира. Доставка: sync HTTP 503 + async (webhook invoice.status_changed, status=error) |
| `kaspi_session_unavailable` (#kaspi_session_unavailable) | 503 | Не удалось проверить сессию Kaspi — попробуйте позже. Доставка: sync HTTP 503 |
| `manager_throttled` (#manager_throttled) | 429 | Слишком много операций — попробуйте позже. Доставка: sync HTTP 429 |
| `whatsapp_otp_throttled` (#whatsapp_otp_throttled) | 429 | Слишком частые запросы кода WhatsApp — попробуйте позже. Доставка: sync HTTP 429 |
| `whatsapp_gateway_error` (#whatsapp_gateway_error) | 500 | Не удалось отправить код WhatsApp — попробуйте ещё раз. Доставка: sync HTTP 500 |
| `subscription_payment_failed` (#subscription_payment_failed) | 502 | Не удалось создать счёт для оплаты подписки — попробуйте позже. Доставка: sync HTTP 502 |
| `kaspi_error` (#kaspi_error-2) | 502 | Kaspi API вернул ошибку — повторите позже. Текст message содержит конкретную причину от Kaspi. Доставка: sync HTTP 502 + async (для QR-счетов: webhook invoice.status_changed, status=error) |
| `trial_daily_limit` (#trial_daily_limit) | 429 | Антифрод/триал: на пробном тарифе превышен дневной лимит создания счетов (50 счетов/сутки). Дождитесь следующего дня или оформите подписку. Доставка: sync HTTP 429 + заголовок Retry-After |
| `outstanding_recipient_limit` (#outstanding_recipient_limit) | 429 | Антифрод: слишком много неоплаченных (outstanding) счетов на одного получателя. Дождитесь оплаты или истечения ранее выставленных счетов. Доставка: sync HTTP 429 + заголовок Retry-After |
| `outstanding_org_limit` (#outstanding_org_limit) | 429 | Антифрод: слишком много неоплаченных (outstanding) счетов по организации в целом. Дождитесь оплаты или истечения ранее выставленных счетов. Доставка: sync HTTP 429 + заголовок Retry-After |
| `recipient_fanout_exceeded` (#recipient_fanout_exceeded) | 429 | Антифрод: превышен темп рассылки счетов по разным получателям (fan-out). Снизьте частоту создания счетов на разные номера. Доставка: sync HTTP 429 + заголовок Retry-After |
| `content_rejected` (#content_rejected) | 422 | Антифрод: содержимое счёта (например текст описания) отклонено проверкой. Исправьте текст и повторите. Доставка: sync HTTP 422 |
| `sandbox_simulated_error` (#sandbox_simulated_error) | — | Симулированная ошибка в песочнице: POST /invoices/{id}/simulate-status со status=error присваивает счёту этот error_code (с опциональным error_message до 255 символов) и шлёт вебхук invoice.status_changed как при реальной ошибке. Только sandbox (в проде simulate-status недоступен). Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
| `cart_items_mismatch` (#cart_items_mismatch) | — | Зарезервирован — сейчас не используется бэкендом (такие случаи приходят как kaspi_error). Доставка: — |
| `image_upload_failed` (#image_upload_failed) | — | Зарезервирован — сейчас не используется бэкендом. Доставка: — |
| `catalog_item_not_found` (#catalog_item_not_found) | — | Зарезервирован — сейчас не используется бэкендом. Доставка: — |

## Каталог: резолв штрихкода (POST /catalog/scan)

| Код | HTTP | Что это и что делать |
|---|---|---|
| `kaspi_session_expired` (#kaspi_session_expired) | 400 | Сессия Kaspi мерчанта истекла — нужна переавторизация кассира Kaspi. Доставка: sync HTTP 400 |
| `kaspi_throttled` (#kaspi_throttled-2) | 429 | Kaspi троттлит сессию при сканировании. Повторить после паузы (тело retry_after_seconds, заголовок Retry-After). Circuit-breaker: ~90 с сразу отдаёт 429 без обращения к Kaspi. Доставка: sync HTTP 429. Примечание: отличается от async error_code kaspi_throttled по счетам |
| `kaspi_scan_unavailable` (#kaspi_scan_unavailable) | 503 | Нацкаталог Kaspi временно недоступен — повторить позже. Доставка: sync HTTP 503 |

## KYC: верификация бизнеса и webhook-домен (новое)

| Код | HTTP | Что это и что делать |
|---|---|---|
| `kyc_daily_limit_reached` (#kyc_daily_limit_reached) | 429 | Молодая организация: пока анкета о бизнесе не одобрена, доступен 1 реальный счёт в сутки (окно Asia/Almaty; счета в песочнице не считаются). Чтобы снять лимит — заполните короткую анкету «Расскажите о бизнесе» в кабинете (/business-profile), одобрение обычно за 1 рабочий день. Не повторяйте запрос до сброса. Доставка: sync HTTP 429 (meta.reset_at — когда лимит сбросится, meta.kyc_status — текущий статус) |
| `kyc_rejected` (#kyc_rejected) | 403 | Приём платежей недоступен по итогам проверки бизнеса (статус организации blocked). Не повторяемая — напишите в поддержку, если считаете это ошибкой. Доставка: sync HTTP 403 при создании счёта (POST /invoices, POST /invoices/qr) |
| `webhook_url_requires_domain` (#webhook_url_requires_domain) | 422 | Адрес webhook должен быть на вашем домене — IP-адреса не принимаются. Действует для ещё не одобренных организаций в рабочем режиме; в песочнице правило мягче. Укажите публичный HTTPS-адрес на домене. Доставка: sync HTTP 422 при сохранении webhook-URL |
| `webhook_url_tunnel_forbidden` (#webhook_url_tunnel_forbidden) | 422 | Туннели (ngrok и подобные) нельзя использовать для рабочих webhook — они временны и отключатся, уведомления перестанут приходить. Укажите адрес на вашем домене. Действует для ещё не одобренных организаций в рабочем режиме; в песочнице туннель для локального теста допустим. Доставка: sync HTTP 422 при сохранении webhook-URL |

## Фискальные чеки (Kaspi OFD)

| Код | HTTP | Что это и что делать |
|---|---|---|
| `fiscal_receipts_disabled` (#fiscal_receipts_disabled) | 403 | Выбивание чеков отключено для боевых организаций (фича включается постепенно). В песочнице чеки работают всегда — обкатайте интеграцию там. Не повторяемая. Доставка: sync HTTP 403 |
| `not_sandbox` (#not_sandbox) | 403 | Поле simulate прислано боевой организацией — форсировать исход чека можно только в песочнице. Чек не создан. Уберите simulate из тела запроса. Доставка: sync HTTP 403 (POST /receipts) |
| `kaspi_session_not_configured` (#kaspi_session_not_configured-2) | 409 | К организации не привязан кассир Kaspi Pay — выбить чек не через кого. Подключите кассира в кабинете (Настройки → Подключение кассира). Доставка: sync HTTP 409 |
| `duplicate_client_operation_id` (#duplicate_client_operation_id) | 409 | Чек с таким client_operation_id уже выбивался — второй раз он не пробьётся (идемпотентность). В теле ответа приходит receipt_id существующего чека: опросите его через GET /receipts/{id}. Повтор после failed разрешён, но с НОВЫМ ключом. Доставка: sync HTTP 409 |
| `connection_ambiguous` (#connection_ambiguous-2) | 422 | К организации привязано несколько кассиров — непонятно, через какого выбивать чек. Передайте kaspi_connection_id явно. Доставка: sync HTTP 422 |
| `receipt_preview_unavailable` (#receipt_preview_unavailable) | 503 | Предпросмотр чека временно недоступен (POST /receipts/preview). Повторяемая — попробуйте позже; на выбивание самого чека не влияет. Доставка: sync HTTP 503 |
| `receipt_not_found` (#receipt_not_found) | 404 | Чек не найден или принадлежит другой организации. Доставка: sync HTTP 404 (GET /receipts/{id}) |
| `shift_closed` (#shift_closed) | — | Смена на кассе закрыта — чек выбить нельзя. Откройте смену в приложении Kaspi Pos и повторите с новым client_operation_id. В песочнице воспроизводится через simulate. Доставка: async, status=failed (GET /receipts/{id}, вебхук receipt.failed) |
| `item_not_fiscal` (#item_not_fiscal) | — | В чеке есть позиция, не зарегистрированная фискально: у товара должны быть и штрихкод (barcode), и НТИН Нацкаталога (ntin). Дозаполните НТИН в каталоге (PATCH /catalog/{id}) — позиция станет фискальной. Позиции без НТИН удобно найти через GET /catalog?without_ntin=true. Правило одинаково в бою и в песочнице. Доставка: async, status=failed |
| `rfo_missing` (#rfo_missing) | — | У кассира не настроен фискальный регистратор (РФО) — Kaspi не может зарегистрировать чек. Проверьте настройки кассы в Kaspi Pos. Доставка: async, status=failed |
| `receipt_kaspi_error` (#receipt_kaspi_error) | — | Kaspi отклонил выбивание чека; подробности — в поле error_message чека. Повтор возможен с НОВЫМ client_operation_id. В песочнице воспроизводится через simulate. Доставка: async, status=failed |
| `receipt_dispatch_error` (#receipt_dispatch_error) | — | Не удалось отправить чек в Kaspi (сеть или временный сбой). Повторяемая — попробуйте ещё раз с новым client_operation_id. Доставка: async, status=failed |

---

ApiPay — независимый сервис и не аффилирован с АО «Kaspi Bank». Kaspi и Kaspi Pay — товарные знаки их правообладателя.
База знаний: https://apipay.kz/guides
