Каталог кодов ошибок ApiPay: HTTP-статусы, значения поля error, стабильные error_code и асинхронные ошибки Kaspi. У каждого кода — постоянный якорь #код: возьмите error_code из ответа API и откройте /errors#код.
Ошибки приходят в нескольких формах — не путайте их между собой:
400, 401, 429 …) — общий класс ошибки, присутствует всегда.error в теле ответа — конкретная причина синхронной ошибки. Значения бывают
двух видов: машинные коды в snake_case (organization_required,
kaspi_session_not_configured) и английские фразы целиком
(Organization not found or not verified, Invoice cannot be cancelled).
Не сравнивайте текст error в коде — для ветвления используйте error_code.error_message — человекочитаемый текст асинхронной ошибки Kaspi (счёт создан
со статусом processing и позже перешёл в error). Фиксированных кодов у Kaspi нет.error_code (новое) — стабильный snake_case-код из фиксированного каталога.
Стройте switch-логику по нему, а не по тексту error/error_message.В колонке «Код» ниже значения сгруппированы по тому, где именно они появляются.
| Код | HTTP | Что это и что делать |
|---|---|---|
400 # | 400 | Bad Request — некорректный запрос или недопустимое состояние. Точная причина — в поле message или error |
401 # | 401 | Unauthorized — API-ключ отсутствует, неверен, истёк или не привязан к организации; либо аккаунт деактивирован |
403 # | 403 | Forbidden — организация заморожена (suspended) или не верифицирована для рабочего режима |
tariff_inactive # | 403 | Нет действующей подписки на ApiPay — оплатите тариф в кабинете. Закрывает все платные операции: создание, отмену и возврат счёта, чеки, изменяющий каталог, создание, изменение и возобновление подписок, проверку номера клиента. Грейса нет — блокировка сразу после expires_at (это поле приходит в теле ответа; null, если тариф не оформлялся). Чтение (GET), оплата тарифа, настройки, подключение кассира, а также приостановка и отмена подписок продолжают работать. В песочнице тариф не требуется |
404 # | 404 | Not Found — ресурс не найден или принадлежит другой организации |
422 # | 422 | Validation Error (машинный код validation_error) — ошибка валидации полей; детали в объекте errors. Например, POST /catalog/image без файла image отвечает именно так. Повтор того же запроса даст тот же результат — правьте запрос. |
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 | Server Error — внутренняя ошибка сервера |
502 # | 502 | Bad Gateway — ошибка на стороне Kaspi API |
503 # | 503 | Service Unavailable — сессия Kaspi недействительна или истекла |
organization_archived # | 403 | Организация, к которой привязан ключ, отправлена в архив. Отказ приходит на ЛЮБУЮ операцию этого ключа. Сам ключ остаётся активным, и перевыпуск ничего не меняет — доступ возвращает владелец аккаунта. Не путайте с 401: там ключ неверен, истёк или деактивирован, и перевыпуск как раз помогает. Что именно случилось с организацией, ответ не раскрывает. |
| Код | HTTP | Что это и что делать |
|---|---|---|
grant_not_granted # | 403 | Операция вне набора прав, который мерчант выдал вашей интеграции. Один и тот же ответ приходит на три случая — права нет, операция интеграциям не выдаётся никогда, операции нет в каталоге прав. По ответу они не различаются. Повтор бессмысленен — набор меняет мерчант в своём кабинете. |
channel_required # | 403 | Живого доступа к организации у интеграции нет: мерчант его отозвал. Ключ при этом продолжает существовать и не деактивируется — доступ восстанавливает мерчант. Отдельный код от grant_not_granted намеренно: «не хватает права» и «доступа нет вовсе» лечатся по-разному. |
grant_snapshot_unreadable # | 403 | Права интеграции временно недоступны — это временный сбой на нашей стороне, а не отказ в праве. Повторите позже; если отказ повторяется — обратитесь в поддержку. |
| Код | HTTP | Что это и что делать |
|---|---|---|
organization_required # | 400 | Организация не подключена — создайте sandbox-организацию для тестов или подключите кассира Kaspi |
Organization not found or not verified # | 400 | Рабочий режим: организация не верифицирована. Дождитесь верификации или тестируйте в песочнице |
kaspi_session_not_configured # | 400 | Кассир Kaspi не подключён. Подключите его в кабинете (Настройки → Авторизация Kaspi) или через поддержку (WhatsApp +7 700 307 65 12) |
kaspi_session_invalid # | 503 | Сессия кассира Kaspi истекла или сброшена. Переподключите кассира — запросите новый SMS-код |
connection_ambiguous # | 422 | У организации несколько активных касс, основная не выбрана — передайте kaspi_connection_id |
sandbox_invoice_limit # | 400 | Достигнут лимит тестовых счетов (1000 на организацию) — очистите песочницу в кабинете |
duplicate_idempotency_key # | 409 | Идемпотентность: активный счёт с таким external_order_id_idempotency уже существует — повторный POST /invoices с тем же ключом не создаёт дубликат (в ответе invoice_id и status существующего счёта). Перевыставление возможно, только если предыдущий счёт с этим external_order_id_idempotency находится в статусе expired, cancelled или error |
amount_must_be_whole_tenge # | 422 | Сумма счёта на номер телефона должна быть целой: тиыны Kaspi по такому счёту не принимает. Проверяется и голая amount, и итог корзины после скидок. Округлите сумму или выставьте счёт через POST /invoices/qr — там суммы с тиынами принимаются. В POST /invoices/bulk приходит по позиции: она попадает в invoices[] как failed, остальные создаются |
invoices_disabled # | 503 | Приём новых счетов временно приостановлен — идут технические работы. Счёт не создан, повторите позже. |
| Код | HTTP | Что это и что делать |
|---|---|---|
qr_rate_limit # | 429 | Слишком много QR-запросов для организации (лимит 60/мин) — подождите минуту. Заголовок Retry-After на этом 429 не возвращается (в отличие от общего лимита Public API 200/мин) |
qr_render_failed # | 500 | Не удалось сформировать изображение QR-кода — повторите запрос позже |
kaspi_error # | 502 | Kaspi API вернул ошибку при создании QR-токена — повторите позже |
| Код | HTTP | Что это и что делать |
|---|---|---|
Invoice cannot be cancelled # | 400 | Отменить можно только счёт в статусе pending или processing |
Invoice is not refundable # | 400 | Возврат возможен только по оплаченному счёту, ещё не возвращённому полностью |
Refund amount exceeds available amount # | 400 | Сумма возврата больше доступной — смотрите available_for_refund в GET /invoices/{id} |
qr_cancel_unsupported # | 409 | QR-счёт (is_qr_token: true) отменить нельзя — отмена для него не поддерживается. Статус счёта не меняется, в Kaspi ничего не уходит. В теле ответа есть expires_at — момент, после которого QR перестанет быть оплачиваемым; дождитесь статуса expired или выставьте новый счёт |
| Код | HTTP | Что это и что делать |
|---|---|---|
status=error # | — | Счёт создан (201, статус processing), но Kaspi не смог его обработать — статус сменился на error. Причина текстом в поле error_message (GET /invoices/{id}). У Kaspi нет фиксированных кодов — текст приходит как есть |
error_message: номер не в Kaspi # | — | «Этот номер телефона не зарегистрирован в Kaspi. Укажите номер с установленным приложением Kaspi.» — у клиента нет приложения Kaspi; попросите другой номер |
error_message: сбой Kaspi # | — | «Ошибка обработки платежа. Обратитесь в поддержку» или «Не удалось обработать счёт после нескольких попыток» — временный сбой Kaspi; повторите создание счёта позже |
| Код | HTTP | Что это и что делать |
|---|---|---|
error_code (новое поле) # | — | Стабильный snake_case-код ошибки из фиксированного каталога. Присутствует в JSON-ответах об ошибках и в webhook-объектах invoice (для status=error) и refund (для status=failed). Поля message/error сохранены без изменений. Определяйте тип ошибки по error_code, текст — для показа пользователю. У каждого кода ниже указана «Доставка» — приходит ли он асинхронно (в webhook) или синхронно (HTTP-ответ с кодом) |
network_unavailable # | — | Сервис временно недоступен (сбой сети/Kaspi). Можно повторить позже. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
session_transient # | — | Временные проблемы авторизации Kaspi. Можно повторить попытку. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
client_not_found # | — | Номер телефона не зарегистрирован в Kaspi. Не повторяемая — попросите другой номер. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
kaspi_throttled # | — | Kaspi ограничил частоту запросов. Повторите через 2–3 минуты. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
refund_window_expired # | — | Срок возврата истёк или возврат уже сделан (часто для refund status=failed). Доставка: async — приходит в invoice.refunded (refund.error_code) |
refund_rejected_by_kaspi # | — | Kaspi отклонил возврат по этой операции. Отказ может быть временным — попробуйте позже ещё раз. Если не получится, выполните возврат вручную в приложении Kaspi Pay. Сервис сам этот возврат не повторяет: строка сразу финальная, повтор — ваш новый запрос на возврат. Доставка: async — приходит в invoice.refunded (refund.error_code) |
refund_requires_buyer_confirmation # | — | Kaspi требует подтверждение покупателя. Создайте QR-возврат через POST /api/v1/qr-refunds и попросите покупателя отсканировать QR-код. Доставка: async — приходит в invoice.refunded (refund.error_code) |
invoice_already_paid # | — | Счёт уже оплачен. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
invoice_already_cancelled # | — | Счёт уже отменён. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
invoice_not_found_in_kaspi # | — | Счёт не найден в Kaspi. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
organization_not_configured # | — | Организация не настроена (нет рабочей привязки Kaspi). Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
unknown_error # | — | Непредвиденная ошибка обработки — обратитесь в поддержку. Доставка: async — приходит в invoice.status_changed (invoice.error_code) |
qr_render_failed # | 500 | Не удалось сформировать изображение QR-кода — повторите запрос. Доставка: sync HTTP 500 + async (webhook invoice.status_changed, status=error) |
kaspi_session_invalid # | 503 | Сессия кассира Kaspi истекла или сброшена — переподключите кассира. Доставка: sync HTTP 503 + async (webhook invoice.status_changed, status=error) |
kaspi_session_unavailable # | 503 | Не удалось проверить сессию Kaspi — попробуйте позже. Доставка: sync HTTP 503; на GET /invoices/{id}/receipt этот код приходит с HTTP 409 |
manager_throttled # | 429 | Слишком много операций — попробуйте позже. Доставка: sync HTTP 429 |
whatsapp_otp_throttled # | 429 | Слишком частые запросы кода WhatsApp — попробуйте позже. Доставка: sync HTTP 429 |
whatsapp_gateway_error # | 500 | Не удалось отправить код WhatsApp — попробуйте ещё раз. Доставка: sync HTTP 500 |
subscription_payment_failed # | 502 | Не удалось создать счёт для оплаты подписки — попробуйте позже. Доставка: sync HTTP 502 |
kaspi_error # | 502 | Kaspi API вернул ошибку — повторите позже. Текст message содержит конкретную причину от Kaspi. Доставка: sync HTTP 502 + async (для QR-счетов: webhook invoice.status_changed, status=error) |
trial_daily_limit # | 429 | Антифрод/триал: на пробном тарифе превышен дневной лимит создания счетов (50 счетов/сутки). Дождитесь следующего дня или оформите подписку. Доставка: sync HTTP 429 + заголовок Retry-After |
tariff_limit_reached # | 429 | Исчерпан лимит счетов оплаченного тарифа (Старт 30, Бизнес 100, Про 300, Про Макс 600 в сутки). Считаются только счета, созданные через API; счета из кабинета и песочницы в лимит не входят. Разовое превышение не блокирует: отказ приходит при систематическом превышении лимита либо при исчерпанном бюджете у организаций на помесячном подсчёте. meta.mode различает окна (daily — расчётные сутки, monthly — блок 30 дней), meta.limit — потолок, meta.used — израсходовано, meta.reset_at — момент обнуления счётчика; повторять запрос раньше бессмысленно. Ограничение снимается переходом на тариф выше сразу после оплаты. Автосписания по подпискам, созданным через API, при действующем ограничении пропускают цикл, дата следующего списания не сдвигается. В POST /invoices/bulk отказ приходит поэлементно и несёт только error_code и message. Доставка: sync HTTP 429 + заголовок Retry-After |
outstanding_recipient_limit # | 429 | Антифрод: слишком много неоплаченных (outstanding) счетов на одного получателя. Дождитесь оплаты или истечения ранее выставленных счетов. Доставка: sync HTTP 429 + заголовок Retry-After |
outstanding_org_limit # | 429 | Антифрод: слишком много неоплаченных (outstanding) счетов по организации в целом. Дождитесь оплаты или истечения ранее выставленных счетов. Доставка: sync HTTP 429 + заголовок Retry-After |
recipient_fanout_exceeded # | 429 | Антифрод: превышен темп рассылки счетов по разным получателям (fan-out). Снизьте частоту создания счетов на разные номера. Доставка: sync HTTP 429 + заголовок Retry-After |
content_rejected # | 422 | Антифрод: содержимое счёта (например текст описания) отклонено проверкой. Исправьте текст и повторите. Доставка: sync HTTP 422 |
description_too_long # | 422 | Описание счёта длиннее, чем Kaspi показывает покупателю: видны только первые 60 символов, остальное Kaspi отбрасывает. Укоротите описание — вынесите в начало то, по чему плательщик узнает платёж. Повтор с тем же телом бесполезен. Доставка: sync HTTP 422 |
field_too_long # | 422 | Значение поля слишком длинное. Имя поля лежит ключом в errors — укоротите именно его; повтор с тем же телом бесполезен. Страховочный код: обычно слишком длинное значение отсекается обычной ошибкой валидации, форма которой не менялась. В POST /invoices/bulk приходит не на весь запрос, а строкой в invoices[] у конкретной позиции: ответ остаётся 201, соседние счета созданы, повторить нужно только эту позицию. Доставка: sync HTTP 422 |
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 # | — | Зарезервирован — сейчас не используется бэкендом (такие случаи приходят как kaspi_error). Доставка: — |
image_upload_failed # | — | Зарезервирован — сейчас не используется бэкендом. Доставка: — |
catalog_item_not_found # | — | Зарезервирован — сейчас не используется бэкендом. Доставка: — |
| Код | HTTP | Что это и что делать |
|---|---|---|
catalog_batch_filter_removed # | 422 | Фильтр по партии (batch_id) больше не поддерживается: агрегат партий удалён. Отбивается по самому наличию параметра, поэтому пустой batch_id тоже вернёт 422 — это сделано намеренно, чтобы старый интегратор увидел отказ, а не молча получил не тот набор. Подтверждайте свой набор точечным запросом GET /catalog?external_refs[]=..., остаток работы смотрите в GET /catalog/queue, отказы — в GET /catalog/errors с окном from и to по моменту отказа, push — событие catalog.item_processed. Доставка: sync HTTP 422 |
| Код | HTTP | Что это и что делать |
|---|---|---|
kaspi_session_expired # | 400 | Сессия Kaspi мерчанта истекла — нужна переавторизация кассира Kaspi. Доставка: sync HTTP 400 |
kaspi_throttled # | 429 | Kaspi троттлит сессию при сканировании. Повторить после паузы (тело retry_after_seconds, заголовок Retry-After). Circuit-breaker: ~90 с сразу отдаёт 429 без обращения к Kaspi. Доставка: sync HTTP 429. Примечание: отличается от async error_code kaspi_throttled по счетам |
kaspi_scan_unavailable # | 503 | Нацкаталог Kaspi временно недоступен — повторить позже. Доставка: sync HTTP 503 |
| Код | HTTP | Что это и что делать |
|---|---|---|
catalog_item_invalid # | — | Позиция не прошла валидацию: имя, цена, единица измерения или другое поле. Конкретика — в error_message и карте errors по позиции. Доставка: per-item в rejected[] ответа POST /catalog |
catalog_item_duplicate # | — | Kaspi отклонил товар как похожий на уже существующий в вашем каталоге. Проверьте, нет ли позиции с тем же названием и штрихкодом, — если товар действительно новый, измените название так, чтобы оно отличалось |
barcode_too_long # | — | Штрихкод длиннее допустимого — не более 32 символов. Обрежьте значение или передайте позицию без штрихкода |
catalog_delivery_incomplete # | — | Позицию не удалось довезти до Kaspi — Kaspi её ни разу не видел и данные не отвергал. Чинить в позиции нечего: переотправьте её тем же POST /catalog с тем же external_ref, данные менять не нужно. Если вы шлёте Idempotency-Key, повтор должен идти с НОВЫМ ключом, иначе ответ вернётся из кеша идемпотентности и работа по строке не откроется. Раньше этот случай приходил под кодом catalog_item_invalid. Доставка: error_code строки каталога (GET /catalog/errors, событие catalog.item_processed) |
catalog_create_unresolved # | — | Запрос на создание ушёл в Kaspi, но подтверждения по позиции так и не пришло, и попытки прекращены. Данные позиции ни при чём. Прежде чем заводить товар заново, посмотрите каталог в приложении Kaspi Pay: он мог всё-таки создаться, и повторное заведение даст две одинаковые позиции. Дожать строку — PATCH /catalog/{id} («Повторить» в кабинете); дословный повтор POST /catalog такую строку не возобновляет. Доставка: error_code строки каталога (GET /catalog/errors, событие catalog.item_processed) |
catalog_create_blocked # | — | Записывать каталог от имени организации было невозможно слишком долго — например, подключение кассира перестало быть активным, — и заявка на создание закрыта по сроку. Данные позиции ни при чём: сначала восстановите подключение, затем дожмите строку через PATCH /catalog/{id} («Повторить» в кабинете). Дословный повтор POST /catalog такую строку не возобновляет. Доставка: error_code строки каталога (GET /catalog/errors, событие catalog.item_processed) |
| Код | HTTP | Что это и что делать |
|---|---|---|
catalog_delete_scope_required # | 422 | Не задан ни ids[], ни external_refs[], либо заданы оба списка сразу. Передайте ровно один список — сервер не додумывает, что именно вы хотите удалить |
catalog_match_overflow # | 422 | В списке слишком много значений — за один запрос принимается не более 200 ids или external_refs. Разбейте выборку на части |
catalog_bulk_delete_mismatch # | 409 | Переданный expected_count разошёлся с фактом: каталог изменился между разведкой и командой. В теле придёт actual_count — повторите dry_run и убедитесь, что удаляете то, что хотели. Поле expected_count необязательное, но при расхождении не удаляется ничего |
catalog_multi_tradepoint # | 409 | У организации несколько торговых точек — массовое удаление для неё закрыто. Обратитесь в поддержку. У одиночного DELETE /catalog/{id} этот же код синхронно больше не приходит: запрос принимается с 202, а код появляется на самой позиции в error_code |
catalog_busy # | 409 | Каталог занят другой операцией — повторите запрос через несколько секунд |
idempotency_key_conflict # | 409 | Переданный Idempotency-Key уже занят другим телом либо другой каталожной операцией — пространство ключей общее. Возьмите новый ключ. Точный повтор ТОГО ЖЕ тела с тем же ключом конфликтом не считается: приходит 200 с idempotent_replay true, и ничего не удаляется повторно |
request_rate_limited # | 429 | Превышен лимит эндпоинта — 10 запросов в минуту. Разведка (dry_run) расходует тот же лимит. Пауза — в поле retry_after_seconds и в заголовке Retry-After |
| Код | HTTP | Что это и что делать |
|---|---|---|
catalog_item_foreign_channel # | 409 | Позиция заведена другой интеграцией этой организации: изменить и снять её можно только со стороны той интеграции либо из кабинета мерчанта. Отказ именно 409, а не 404, потому что позиция видна: в списке она приходит с source: other. ⚠️ Повторная заливка ошибки не даёт: POST /catalog при совпадении external_ref, barcode или ntin вернёт её id с matched_existing: true и НЕ обновит ни имя, ни цену — прежде чем считать позицию синхронизированной, сверяйте source. Позиций, у которых интеграции-автора нет (source: shared), это не касается. |
| Код | HTTP | Что это и что делать |
|---|---|---|
custom_tariff_locked # | 409 | У мерчанта индивидуальные условия тарифа: смена тарифа не самообслуживаемая и оформляется поддержкой. Продление ТОГО ЖЕ тарифа не блокируется — это оплата, а не смена условий. Определяйте состояние заранее: is_custom в GET /api/v1/tariff и can_change_tier в каталогах планов; повтор запроса не поможет |
| Код | HTTP | Что это и что делать |
|---|---|---|
catalog_requires_cart_items # | 422 | У организации включён каталог товаров: счёт должен нести состав покупки. Передайте cart_items[] — идентификаторы позиций берутся из GET /catalog, цена строки при необходимости переопределяется полем price. Приходит на POST /invoices/qr и POST /static-qr. Создание подписки (POST /subscriptions) корзину тоже требует, но отвечает обычной ошибкой валидации по полю cart_items, без этого кода. Доставка: sync HTTP 422 (без ключа errors) |
catalog_not_supported # | 422 | У организации каталог товаров выключен, а в запросе передана корзина. Уберите cart_items[] и передайте amount. Если каталог вам нужен — он включается вместе с Kaspi Кассой (ОФД), напишите нам. Доставка: sync HTTP 422 (без ключа errors), а в POST /invoices/bulk — per-item в разборе позиций |
| Код | HTTP | Что это и что делать |
|---|---|---|
duplicate_cashier_confirm # | 409 | Этот номер кассира уже подключён к другой организации того же владельца. Это не блокировка: повторите тот же запрос send-phone с confirm_duplicate: true. В теле — can_override и existing_organization с полями id, name, connection_status и invoices_count; connection_status относится к подключению, а не к организации |
entrance_auth_disabled # | 503 | Подключение кассира временно недоступно — идут технические работы. Повторите позже, данные подключения не пострадали |
cashier_change_requires_merchant # | 403 | Сменить кассира у этой организации может только сам мерчант. Отказ приходит на send-phone, когда ключ выдан партнёром на организацию, аккаунтом которой партнёр не владеет, и присланный номер отличается от телефона текущего кассира. Первое подключение (кассира ещё нет) и переавторизация того же кассира проходят. Повтор не поможет: смену проводит мерчант — своей дверью либо по ссылке-приглашению. |
context_expired # | 409 | Процесс авторизации кассира закрыт: контекст истёк либо Kaspi его потерял. Исход терминальный — повторять send-phone или verify-otp бесполезно, начните заново с POST /connections/{connection}/auth/init. Уже подтверждённая ранее привязка не сбрасывается. |
kaspi_busy # | 503 | Kaspi временно не принимает этот шаг. Приходит на шагах send-phone и verify-otp; в заголовке Retry-After — сколько секунд ждать. Сессия авторизации при этом закрыта: после паузы нужен новый POST /connections/{connection}/auth/init, а не повтор того же шага. |
sms_failed # | 502 | Kaspi не отправил кассиру SMS с кодом. Шаг send-phone можно повторить. Если отказ повторяется, проверьте, что номер заведён кассиром в Kaspi Pay. |
kyc_required # | 403 | Анкета «Расскажите о бизнесе» этой организации ещё не одобрена — подключить кассира можно после одобрения. Приходит на любом из трёх шагов: init, send-phone и verify-otp. Повтор до одобрения бесполезен: на send-phone SMS кассиру не отправляется вовсе, попытка ничего не меняет. На verify-otp отказ терминальный и встречается редко (одобрение отозвали между send-phone и verify-otp): код Kaspi уже принял, но подключение не создаётся, сессия закрыта — после одобрения анкеты нужен новый init. Переподключение уже привязанного кассира (тот же номер) этим кодом не отбивается. Анкету подаёт сам мерчант — в кабинете на /business-profile либо по персональной ссылке. Доставка: sync HTTP 403 |
| Код | HTTP | Что это и что делать |
|---|---|---|
organization_identity_conflict # | 409 | Подключаемый кассир принадлежит другой организации Kaspi. Организация закрепляется за первой привязкой и подключением нового кассира не переносится. Подключайте кассира той организации, за которой эта уже закреплена, либо заводите отдельную организацию. В теле приходят только error и message: идентификаторов и названия другой организации в ответе нет. Если владелец бизнеса действительно сменился, напишите в поддержку 77003076512. Доставка: sync HTTP 409 |
organization_identity_unavailable # | 502 | Kaspi не вернул достоверные данные организации, и сверить её в этой попытке не удалось. Попытка входа закрыта: начните заново с POST /connections/{connection}/auth/init, повторная отправка кода на закрытом процессе вернёт no_process. Уже подтверждённая ранее привязка не сбрасывается и не блокируется. В теле приходят только error и message, отдельного поля error_code нет — разбирайте error. Доставка: sync HTTP 502 |
connection_identity_unverified # | 422 | Основным нельзя назначить подключение, которое ещё не подтвердило организацию или заблокировано. Проведите вход кассира до конца и повторите. В теле приходят только error и message, отдельного поля error_code нет — разбирайте error. Доставка: sync HTTP 422 |
| Код | HTTP | Что это и что делать |
|---|---|---|
kyc_daily_limit_reached # | 429 | Молодая организация: пока анкета о бизнесе не одобрена, боевые счета не выставляются — этот код приходит на первый же запрос (meta.limit = 0, окно Asia/Almaty). Песочница работает без ограничений и анкеты не требует. Чтобы снять ограничение — заполните короткую анкету «Расскажите о бизнесе» в кабинете (/business-profile), одобрение обычно за 1 рабочий день. Повторять запрос до одобрения бесполезно. Доставка: sync HTTP 429 (meta.reset_at — момент обнуления счётчика, meta.kyc_status — текущий статус) |
kyc_rejected # | 403 | Приём платежей недоступен по итогам проверки бизнеса (статус организации blocked). Не повторяемая — напишите в поддержку, если считаете это ошибкой. Доставка: sync HTTP 403 при создании счёта (POST /invoices, POST /invoices/qr) |
webhook_url_requires_domain # | 422 | Адрес webhook должен быть на вашем домене — IP-адреса не принимаются. Действует для ещё не одобренных организаций в рабочем режиме; в песочнице правило мягче. Укажите публичный HTTPS-адрес на домене. Доставка: sync HTTP 422 при сохранении webhook-URL |
webhook_url_tunnel_forbidden # | 422 | Туннели (ngrok и подобные) нельзя использовать для рабочих webhook — они временны и отключатся, уведомления перестанут приходить. Укажите адрес на вашем домене. Действует для ещё не одобренных организаций в рабочем режиме; в песочнице туннель для локального теста допустим. Доставка: sync HTTP 422 при сохранении webhook-URL |
| Код | HTTP | Что это и что делать |
|---|---|---|
fiscal_receipts_disabled # | 403 | Выбивание чеков отключено для боевых организаций (фича включается постепенно). В песочнице чеки работают всегда — обкатайте интеграцию там. Не повторяемая. Доставка: sync HTTP 403 |
not_sandbox # | 403 | Поле simulate прислано боевой организацией — форсировать исход чека можно только в песочнице. Чек не создан. Уберите simulate из тела запроса. Доставка: sync HTTP 403 (POST /receipts) |
kaspi_session_not_configured # | 409 | К организации не привязан кассир Kaspi Pay — выбить чек не через кого. Подключите кассира в кабинете (Настройки → Подключение кассира). Доставка: sync HTTP 409 |
duplicate_client_operation_id # | 409 | Чек с таким client_operation_id уже выбивался — второй раз он не пробьётся (идемпотентность). В теле ответа приходит receipt_id существующего чека: опросите его через GET /receipts/{id}. Повтор после failed разрешён, но с НОВЫМ ключом. Доставка: sync HTTP 409 |
connection_ambiguous # | 422 | К организации привязано несколько кассиров — непонятно, через какого выбивать чек. Передайте kaspi_connection_id явно. Доставка: sync HTTP 422 |
receipt_preview_unavailable # | 503 | Предпросмотр чека временно недоступен (POST /receipts/preview). Повторяемая — попробуйте позже; на выбивание самого чека не влияет. Доставка: sync HTTP 503 |
receipt_not_found # | 404 | Чек не найден или принадлежит другой организации. Доставка: sync HTTP 404 (GET /receipts/{id}) |
shift_closed # | — | Смена на кассе закрыта — чек выбить нельзя. Откройте смену в приложении Kaspi Pos и повторите с новым client_operation_id. В песочнице воспроизводится через simulate. Доставка: async, status=failed (GET /receipts/{id}, вебхук receipt.failed) |
item_not_fiscal # | — | В чеке есть позиция, не зарегистрированная фискально: у товара должны быть и штрихкод (barcode), и НТИН Нацкаталога (ntin). Дозаполните НТИН в каталоге (PATCH /catalog/{id}) — позиция станет фискальной. Позиции без НТИН удобно найти через GET /catalog?without_ntin=true. Правило одинаково в бою и в песочнице. Доставка: async, status=failed |
rfo_missing # | — | У кассира не настроен фискальный регистратор (РФО) — Kaspi не может зарегистрировать чек. Проверьте настройки кассы в Kaspi Pos. Доставка: async, status=failed |
receipt_kaspi_error # | — | Kaspi отклонил выбивание чека; подробности — в поле error_message чека. Повтор возможен с НОВЫМ client_operation_id. В песочнице воспроизводится через simulate. Доставка: async, status=failed |
receipt_dispatch_error # | — | Не удалось отправить чек в Kaspi (сеть или временный сбой). Повторяемая — попробуйте ещё раз с новым client_operation_id. Доставка: async, status=failed |
receipt_ofd_token_revoked # | — | Фискальная привязка кассы отозвана — мерчанту нужно перепривязать ОФД в приложении Kaspi. Приём оплат при этом работает: счета и QR продолжают создаваться, встают только чек и изменяющий каталог. Не путайте с kaspi_session_invalid — платёжная сессия жива, переподключение кассира не поможет. Повтор имеет смысл только ПОСЛЕ перепривязки ОФД, временем не лечится. Доставка: async, status=failed |
| Код | HTTP | Что это и что делать |
|---|---|---|
receipt_not_available_for_status # | 409 | Чек есть только у оплаченного или частично возвращённого счёта. Проверьте status счёта перед запросом. Тот же код приходит, если у оплаченного счёта ещё нет числового идентификатора Kaspi — чек по такому счёту получить нельзя. Не повторяемая. Доставка: sync HTTP 409 |
kaspi_session_expired # | 409 | Кассир, через которого прошла оплата, требует переподключения — чек получить нельзя, пока кассир не подключён заново. Счёт и оплата не затронуты. Доставка: sync HTTP 409 |
kaspi_session_unavailable # | 409 | Кассир по счёту сейчас недоступен — повторите позже; если повторяется, проверьте подключение кассира в кабинете. Доставка: sync HTTP 409 |
receipt_rate_limited # | 429 | Запросов чеков слишком много — у ручки отдельный лимит. Повторите через минуту. Доставка: sync HTTP 429 |
receipt_unavailable # | 503 | Чек получить не удалось. Повторяемая — попробуйте через минуту. Счёт и оплата не затронуты. Доставка: sync HTTP 503 |
| Код | HTTP | Что это и что делать |
|---|---|---|
qr_refund_not_identified # | 409 | Покупатель ещё не отсканировал возвратный QR. Приходит на GET /qr-refunds/{id}/operations и на execute. Не ошибка интеграции — дождитесь статуса customer_identified (поллинг GET /qr-refunds/{id} или вебхук qr_refund.identified). Сессия жива. |
qr_refund_expired # | 409 | Срок сессии истёк. У сессии ДВА срока: сам QR живёт минуты (expires_at), и отдельно ограничено время на выбор операции после подтверждения покупателем. Возврат не сделан — начните новую сессию. Не повторяемая. |
qr_refund_completed # | 409 | Возврат по этой сессии уже выполнен. Повторный execute не пройдёт (идемпотентность). Не считайте это отказом: запросите GET /qr-refunds/{id} и возьмите refunded_amount и receipt_url оттуда. |
operation_not_returnable # | 422 | Kaspi не разрешает возврат по выбранной операции (returnable: none) — срок возврата истёк либо деньги уже возвращены. Сессия остаётся живой: выберите другую покупку. |
refund_amount_exceeds_available # | 422 | Сумма больше доступной к возврату. Актуальное значение — в available_for_refund из GET /qr-refunds/{id}/operations/{ref}. Сессия жива, можно повторить с корректной суммой. |
refund_insufficient_funds # | 422 | На счёте Kaspi Pay мерчанта не хватает денег на возврат. Отказ приходит ДО отправки денег: ничего не списано, сессия остаётся customer_identified. Пополните счёт Kaspi Pay — и повторите возврат на той же сессии, пока не истёк срок идентификации покупателя; после него понадобится новая ссылка. Второй выход — вернуть покупателю наличными. Раньше этот отказ приходил как 502 kaspi_error, и повтор без пополнения счёта давал тот же результат. Тот же код приходит асинхронно в refund.error_code вебхука invoice.refunded (status=failed) для POST /invoices/{id}/refund. |
partial_refund_requires_return_items # | 422 | Эту покупку Kaspi возвращает только по позициям: пришлите items вместо amount. Напоминание: amount и items взаимоисключимы. |
not_found # | 404 | Сессия не найдена или принадлежит другой организации — код ответа одинаков в обоих случаях (не оракул чужих сессий). Начните новую сессию. |
not_sandbox # | 403 | Поле simulate или POST /qr-refunds/{id}/simulate прислан боевой организацией — форсировать шаги покупателя можно только в песочнице. |
qr_refund_execution_uncertain (202 своему запросу / 409 повтору) # | 202 | ⛔ Самый важный код группы. Денежный запрос ушёл, а ответ Kaspi не доказал ни успех, ни отказ — возврат мог быть применён. Своему запросу приходит 202 со снимком сессии в статусе execution_uncertain, повтору того же execute — 409. Сессия терминальна для автоматики. Повторять execute НЕЛЬЗЯ ни при каких условиях: второй запрос — это второй возврат живых денег покупателю. Retry-After не отдаётся. Разбирается вручную — обратитесь в поддержку. До ответа поддержки не проводите этот возврат ни повторным execute, ни вручную в приложении Kaspi Pay — деньги могли уже уйти. Параллельно приходит вебхук qr_refund.execution_uncertain. Доставка: sync HTTP 202 и HTTP 409 (POST /qr-refunds/{id}/execute) |
qr_refund_execution_result_unavailable # | 202 | ⛔ Денежный запрос ушёл, а его исход не доказан — деньги могли уйти. Снимка сессии в этом ответе нет: не ждите её полей и не выводите статус из их отсутствия. Повторять execute НЕЛЬЗЯ: попытка уже потрачена. Retry-After не отдаётся. Разбирается вручную — обратитесь в поддержку. Доставка: sync HTTP 202 (POST /qr-refunds/{id}/execute) |
qr_refund_execution_in_progress # | 409 | ⛔ Возврат по этой сессии уже идёт: его начал предыдущий запрос. Сам этот 409 денег не двигает, но повторять execute НЕЛЬЗЯ — второй запрос рискует стать вторым возвратом. Ретрай на этот код не строить. Дождитесь терминального состояния через GET /qr-refunds/{id} или вебхук. Доставка: sync HTTP 409 (POST /qr-refunds/{id}/execute) |
kaspi_error # | 502 | Kaspi недоступен либо ответил неразбираемо до отправки денег. Деньги не двигались, сессия остаётся customer_identified. Повтор здесь допустим: попробуйте ещё раз, пока жива сессия. Доставка: sync HTTP 502 (POST /qr-refunds/{id}/execute) |
qr_refund_execution_context_unavailable # | 503 | Условия возврата не удалось проверить достоверно, поэтому денег мы не трогали: ни одного вызова Kaspi не сделано. Сессия остаётся customer_identified и жива. Повторить execute можно — попробуйте позже, пока не истёк срок сессии. Доставка: sync HTTP 503 (POST /qr-refunds/{id}/execute) |
qr_refund_execution_disabled # | 503 | Денежное выполнение возвратов сейчас закрыто. Отказ происходит до отправки денег: ни одного вызова Kaspi не сделано, деньги не двигались, сессия остаётся customer_identified. Повторить execute можно позже; если код держится, обратитесь в поддержку. Доставка: sync HTTP 503 (POST /qr-refunds/{id}/execute) |
qr_refund_actor_no_longer_authorized # | 403 | Доступ, которым начат возврат, к моменту отправки денег уже не действует: ключ отозван или просрочен, канал закрыт, доступ к организации потерян. Это отдельный код, а не tariff_inactive: у организации право может быть в полном порядке, вопрос к конкретному ключу. Отказ до отправки денег — деньги не двигались, сессия жива. Перевыпустите ключ или восстановите права и повторите execute. Доставка: sync HTTP 403 (POST /qr-refunds/{id}/execute) |
qr_refund_temporarily_unavailable # | 503 | Операция временно недоступна: приходит на выпуск ссылки POST /qr-refunds/links, на её отзыв и на старт POST /qr-refunds. Ни одного вызова Kaspi не сделано, ничего не создано и не списано. Повторить можно позже. Доставка: sync HTTP 503 |
qr_refund_link_limit_reached # | 429 | У организации уже максимум неиспользованных ссылок на возврат — до 20 одновременно. Ссылка не создана, Kaspi не вызывался, ничего не потрачено. Дождитесь, пока покупатели откроют выпущенные ссылки, либо отзовите лишние через DELETE /qr-refunds/links/{id} — и повторите выпуск. Доставка: sync HTTP 429 (POST /qr-refunds/links) |
qr_refund_link_not_revocable # | 409 | Отозвать нечего: покупатель уже открыл ссылку либо она терминальна по другой причине. Это не 404 — ссылка существует и принадлежит вам. Повтор отзыва результата не изменит; текущее состояние читайте через GET /qr-refunds/{id}. Активированная сессия завершится сама по своему окну скана. Доставка: sync HTTP 409 (DELETE /qr-refunds/links/{id}) |
qr_refund_link_revoked # | — | Продавец сам отозвал ссылку на возврат через DELETE /qr-refunds/links/{id}. HTTP-отказом не приходит: это пара error_code/error_message в снимке сессии, статус сессии — expired. Возврат не сделан и по этой ссылке уже не будет. Нужен возврат — выпустите новую ссылку. Доставка: GET /qr-refunds/{id}, ответ DELETE /qr-refunds/links/{id} |
qr_refund_link_expired # | — | Неактивированная ссылка не была использована до своего срока: link_expires_at — 24 часа с выпуска, и он не продлевается ничем, ни открытием страницы, ни перезагрузкой. HTTP-отказом не приходит: пара error_code/error_message в снимке сессии, статус — expired. Возврат не сделан, Kaspi не вызывался. Выпустите новую ссылку. Доставка: GET /qr-refunds/{id} |
qr_return_scan_timeout # | 409 | Окно скана закрылось раньше, чем возвратный QR был выдан покупателю. Окно — не более 90 секунд, действующее значение приходит в scan_wait_timeout_seconds. Возврат не сделан, деньги не двигались, сессия терминальна. Повторять тот же запрос нечем — выпустите новую ссылку POST /qr-refunds/links. Доставка: sync HTTP 409, а также error_code в снимке сессии |
qr_return_identity_timeout # | — | Покупатель подтверждение прошёл, но возврат не был выполнен в отведённое время — здесь не успел продавец, в отличие от qr_return_scan_timeout. HTTP-отказом не приходит: пара error_code/error_message в снимке сессии, статус — expired. Деньги не двигались. Начните возврат заново — новой ссылкой. Доставка: GET /qr-refunds/{id}, вебхук qr_refund.expired |
qr_return_not_found # | — | Kaspi больше не видит эту операцию возврата. HTTP-отказом не приходит: пара error_code/error_message в снимке сессии, статус — failed. Возврат не сделан, деньги не двигались. Повторять нечего — создайте новую ссылку на возврат. Доставка: GET /qr-refunds/{id}, вебхук qr_refund.failed |
qr_refund_activation_in_progress # | 202 | Запрос на выдачу возвратного QR принят и уже ушёл, а исход ещё не записан. Это не ошибка: единственная попытка активации потрачена. Повторять запрос нельзя — ни ссылка, ни старт второй попытки не делают. Состояние читайте через GET /qr-refunds/{id} или ждите вебхук. Доставка: sync HTTP 202 (POST /qr-refunds) |
qr_refund_activation_failed # | 502 | Начать подтверждение не удалось: возвратный QR выдан не был. Статус сессии — failed, возврат не начинался и деньги не двигались. Попытка потрачена: повторять тот же запрос нельзя, нужна НОВАЯ сессия — новая ссылка POST /qr-refunds/links либо новый POST /qr-refunds. Доставка: sync HTTP 502, вебхук qr_refund.failed |
organization_not_verified # | 400 | Организация не верифицирована — возврат по QR ей недоступен. Код приходит на POST /qr-refunds, POST /qr-refunds/links и POST /qr-refunds/{id}/execute. Отказ происходит до обращения в Kaspi: деньги не двигались. Пройдите верификацию в кабинете и повторите. |
| Код | HTTP | Что это и что делать |
|---|---|---|
cashbox_disabled # | 403 | Кассовые операции для организации сейчас недоступны. В песочнице этот отказ не приходит. |
cashbox_kkm_unknown # | 409 | Номер кассы (ККМ) для организации неизвестен. Он есть только у организаций с подключённой кассой Kaspi (ОФД) — той же, что включает каталог товаров. Если продажи идут через Kaspi Pos без ОФД, ждать нечего: кассовых смен у такой организации не существует. Если Kaspi Касса подключена, проверьте, что кассир подключён и его сессия активна; при нескольких кассах укажите kaspi_connection_id нужной точки. Если код продолжает приходить — напишите в поддержку. |
rfo_missing # | 409 | Код торговой точки Kaspi не определён: этим кодом отвечают GET /cashbox/summary и оба тумблера настроек. Чаще всего это значит, что к аккаунту Kaspi Pay не подключена касса Kaspi (ОФД) — тогда кассовых смен у организации не существует. Если Kaspi Касса подключена, при нескольких кассах передавайте kaspi_connection_id нужной точки, а состояние организации пересверяется переподключением кассира или кнопкой «Обновить информацию об организации» в настройках кабинета. ⚠️ Это действие может включить каталог товаров: после него POST /invoices/qr и POST /static-qr без cart_items отвечают 422 catalog_requires_cart_items, а напечатанные QR-листы без состава перестают работать. Тот же код приходит и в фискальном контуре, по той же причине: чек не выбивается, пока код торговой точки не определён. |
cashbox_no_open_shift (см. operation.error_code) # | — | Открытой смены на кассе нет — закрывать нечего. |
cashbox_shift_already_closed # | — | Смена уже закрыта. Для операции закрытия это успешный исход: целевое состояние достигнуто. |
cashbox_shift_not_found # | 404 | Смена с таким id недоступна. Возьмите id из GET /cashbox/shifts. |
cashbox_operation_not_found # | 404 | Операция не найдена или принадлежит другой организации — код ответа одинаков в обоих случаях. |
cashbox_duplicate_operation # | 409 | client_operation_id уже использован. В теле ответа приходит operation_id принятой операции — по нему продолжайте поллинг; для нового закрытия возьмите новый ключ. Ключ не освобождается даже после failed. |
cashbox_busy (см. operation.error_code) # | — | Касса занята другой операцией. Повторите позже новым client_operation_id. |
cashbox_operation_failed (см. operation.error_code) # | — | Kaspi не выполнил операцию. При resolution.safe_to_retry=false автоматический повтор небезопасен — решение оставьте человеку. |
cashbox_unavailable # | 503 | Касса Kaspi временно недоступна, данные получить не удалось. Повторите позже. |
cashbox_report_unavailable # | 503 | Отчёт по смене получить не удалось. Повторите позже. |
cashbox_toggle_in_progress # | 503 | Переключение тумблера уже выполняется. Повторите позже. |
cashbox_toggle_unavailable # | 503 | Текущее значение на кассе проверить не удалось — переключение не выполнено. Повторите позже. |
cashbox_settings_owner_key_required # | 403 | Тумблеры настроек кассы переключает только ключ, выпущенный владельцем организации: PUT /cashbox/settings/auto-close и PUT /cashbox/settings/auto-withdrawal. Ключ сотрудника получает этот отказ, и повтор не поможет — перевыпустите ключ от имени владельца. Чтение GET /cashbox/settings доступно любому ключу организации. |