Полная документация ApiPay REST API v2 — Интернет-эквайринг через Kaspi Pay (Phone Payments)
REST API для приёма платежей по номеру телефона через Kaspi Pay. Без договора с банком, без комиссий. Поддержка каталога, подписок, возвратов и webhooks.
POST /api/v1/invoices| Параметр | Значение |
|---|---|
| Base URL | https://bpapi.bazarbay.site/api/v1 |
| Аутентификация | Header X-API-Key: ваш_api_ключ |
| Rate Limit | 60 запросов/минуту |
| Content-Type | application/json |
| # | Метод | Путь | Описание |
|---|---|---|---|
| Health | |||
| 1 | GET | /status | Проверка доступности (без auth) |
| Счета (Invoices) | |||
| 2 | POST | /invoices | Создать счёт (с корзиной или без) |
| 3 | GET | /invoices | Список счетов |
| 4 | GET | /invoices/{id} | Получить счёт |
| 5 | POST | /invoices/{id}/cancel | Отменить счёт |
| 6 | POST | /invoices/{id}/refund | Возврат по счёту |
| 7 | GET | /invoices/{id}/refunds | Возвраты по счёту |
| 8 | POST | /invoices/status/check | Массовая проверка статусов |
| Возвраты (Refunds) | |||
| 9 | GET | /refunds | Список всех возвратов |
| Каталог (Catalog) | |||
| 10 | GET | /catalog/units | Единицы измерения |
| 11 | GET | /catalog | Список товаров каталога |
| 12 | POST | /catalog/upload-image | Загрузить изображение |
| 13 | POST | /catalog | Создать товары (batch) |
| 14 | PATCH | /catalog/{id} | Обновить товар |
| 15 | DELETE | /catalog/{id} | Удалить товар |
| Подписки (Subscriptions) | |||
| 16 | POST | /subscriptions | Создать подписку |
| 17 | GET | /subscriptions | Список подписок |
| 18 | GET | /subscriptions/{id} | Получить подписку |
| 19 | PUT | /subscriptions/{id} | Обновить подписку |
| 20 | POST | /subscriptions/{id}/pause | Поставить на паузу |
| 21 | POST | /subscriptions/{id}/resume | Возобновить |
| 22 | POST | /subscriptions/{id}/cancel | Отменить подписку |
| 23 | GET | /subscriptions/{id}/invoices | Счета подписки |
Проверка доступности API. Не требует аутентификации.
{
"status": "ok",
"timestamp": "2026-02-12T10:00:00+05:00"
}
Создать счёт на оплату. Два режима: без корзины (передать amount) или с корзиной (передать cart_items, сумма рассчитывается автоматически).
{
"amount": 10000,
"phone_number": "87001234567",
"description": "Оплата заказа #123",
"external_order_id": "order_123"
}
{
"phone_number": "87001234567",
"description": "Оплата заказа #123",
"cart_items": [
{ "catalog_item_id": 1, "count": 2, "price": 4500.00 },
{ "catalog_item_id": 5, "count": 1, "discount": 500 }
],
"discount_percentage": 10
}
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| amount | number | Да (без корзины) | Сумма 0.01 - 99999999.99. Игнорируется при cart_items. |
| phone_number | string | Да | Телефон клиента, формат: 8XXXXXXXXXX |
| description | string | Нет | Макс 500 символов |
| external_order_id | string | Нет | Ваш ID заказа, макс 255 символов |
| cart_items | array | Нет | Товары корзины (только для организаций с каталогом) |
| discount_percentage | number | Нет | Глобальный % скидки (0-100). Применяется к позициям без явного discount. Per-item discount имеет приоритет. |
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| catalog_item_id | integer | Да | ID товара из каталога (поле id из GET /catalog) |
| count | integer | Да | Количество, мин 1 |
| price | number | Нет | Кастомная цена (0.01 - 99999999.99). Заменяет каталожную цену для этой позиции. |
| discount | number | Нет | Скидка на строку товара (мин 0). Применяется к итогу строки (price × count). |
GET /catalog, чтобы использовать корректные catalog_item_id.
{
"id": 124,
"amount": "4550.00",
"status": "pending",
"subtotal": "5000.00",
"discount_sum": "450.00",
"discount_percentage": "10",
"created_at": "2026-02-12T10:35:00+05:00"
}
subtotal, discount_sum и discount_percentage появляются только при наличии скидки (обратная совместимость).
Список счетов с фильтрацией, сортировкой и пагинацией.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
| page | integer | 1 | Номер страницы |
| per_page | integer | 10 | Записей на странице (1-100) |
| search | string | — | Поиск по ID или описанию (макс 100) |
| status[] | array | — | Фильтр: pending, paid, cancelled, expired |
| date_from | string | — | Дата от (YYYY-MM-DD) |
| date_to | string | — | Дата до (YYYY-MM-DD) |
| sort_by | string | created_at | Поле: id, amount, client_name, status, created_at |
| sort_order | string | desc | asc или desc |
{
"current_page": 1,
"data": [
{
"id": 124,
"user_id": 5,
"organization_id": 3,
"api_key_id": 2,
"amount": "5000.00",
"phone_number": "87001234567",
"description": "Оплата заказа #123",
"external_order_id": "order-123",
"status": "paid",
"paid_at": "2026-02-12T10:36:00+05:00",
"created_at": "2026-02-12T10:35:00+05:00"
}
],
"total": 100
}
current_page, total на верхнем уровне — без обёртки meta). Полные данные счёта (в т.ч. client_name, items, поля возвратов) доступны через GET /invoices/{id}.
Получить счёт по ID.
{
"id": 124,
"amount": "5000.00",
"phone_number": "87001234567",
"description": "Payment for order #123",
"external_order_id": "order-123",
"status": "paid",
"client_name": "Ivan I.",
"client_comment": null,
"is_sandbox": false,
"total_refunded": "0.00",
"is_fully_refunded": false,
"kaspi_invoice_id": "13234689513",
"items": [
{
"id": 1,
"invoice_id": 124,
"catalog_item_id": 42,
"name": "Americano",
"price": "800.00",
"original_price": "900.00",
"count": 2,
"discount": null,
"unit_id": 1
},
{
"id": 2,
"invoice_id": 124,
"catalog_item_id": 43,
"name": "Latte",
"price": "1200.50",
"original_price": null,
"count": 1,
"discount": "500.00",
"unit_id": 1
}
],
"paid_at": "2026-02-12T10:36:00+05:00",
"created_at": "2026-02-12T10:35:00+05:00"
}
| Поле | Тип | Описание |
|---|---|---|
| id | integer | ID позиции |
| invoice_id | integer | ID счёта |
| catalog_item_id | integer|null | ID товара из каталога. null если товар удалён |
| name | string | Название товара (snapshot) |
| price | string | Цена за единицу (snapshot) |
| count | integer | Количество |
| unit_id | integer | ID единицы измерения |
| original_price | string|null | Каталожная цена, если переопределена кастомной price. null если использована каталожная цена. |
| discount | string|null | Скидка на строку. null если скидки нет. |
items содержит snapshot товаров на момент создания счёта. Пустой массив [] — организация без каталога или старый счёт. Если catalog_item_id: null — товар был удалён из каталога, но данные (name, price, count, unit_id) сохранены.
Отменить счёт. Только для статуса pending. Может вернуть 202 Accepted — в этом случае счёт переходит в статус cancelling, используйте GET /invoices/{id} для проверки.
{
"message": "Invoice cancelled successfully",
"invoice": {
"id": 124,
"status": "cancelled"
}
}
202 Accepted, счёт переходит в статус cancelling. Используйте GET /invoices/{id} для поллинга до финального статуса cancelled.
Создать полный или частичный возврат. Без amount — полный возврат. Для счетов с товарами используйте return_items для возврата конкретных позиций.
{
"amount": 2000.00,
"reason": "Запрос клиента",
"return_items": [
{ "catalog_item_id": 1, "count": 1 }
]
}
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| amount | number | Нет | Сумма возврата (0.01 - 99999999.99). Без параметра — полный возврат. |
| reason | string | Нет | Причина возврата, макс 500 символов |
| return_items | array | Нет | Конкретные позиции для возврата. Формат: { catalog_item_id, count } |
{
"message": "Refund created and queued for processing",
"refund": {
"id": 456,
"invoice_id": 124,
"amount": "2000.00",
"reason": "Запрос клиента",
"status": "pending",
"created_at": "2026-02-12T11:00:00+05:00"
},
"invoice": {
"id": 124,
"amount": "5000.00",
"total_refunded": "0.00",
"available_for_refund": 5000,
"pending_refund_amount": 2000
}
}
Список возвратов по конкретному счёту с информацией о счёте.
{
"invoice": {
"id": 124,
"amount": "5000.00",
"total_refunded": "2000.00",
"available_for_refund": 3000,
"is_fully_refunded": false
},
"refunds": [
{
"id": 456,
"invoice_id": 124,
"amount": "2000.00",
"status": "completed",
"reason": "Запрос клиента",
"items": null,
"created_at": "2026-02-12T11:00:00+05:00"
}
],
"total": 1
}
Массовая проверка статусов нескольких счетов за один запрос. Полезно для синхронизации состояния в вашей системе.
{
"invoice_ids": [124, 125, 126]
}
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| invoice_ids | array of integers | Да | ID счетов для проверки (макс 100) |
{
"message": "Status check jobs dispatched",
"count": 3
}
GET /invoices/{id} для проверки отдельного счёта после запуска обновления.
| Статус | Описание | Можно отменить | Можно вернуть |
|---|---|---|---|
pending | Ожидает оплаты | Да | Нет |
cancelling | В процессе отмены (async) | Нет | Нет |
paid | Оплачен клиентом | Нет | Да |
cancelled | Отменён | Нет | Нет |
expired | Истёк срок оплаты | Нет | Нет |
partially_refunded | Частично возвращён | Нет | Да (остаток) |
refunded | Полностью возвращён | Нет | Нет |
Управление каталогом товаров. Доступно только для организаций с включённым каталогом (has_catalog = true).
Получить список доступных единиц измерения для товаров каталога.
unit_id.
{
"data": [
{ "id": 1, "name": "шт.", "name_kaz": "дн." },
{ "id": 2, "name": "кг", "name_kaz": "кг" },
{ "id": 3, "name": "литр", "name_kaz": "литр" },
{ "id": 4, "name": "метр", "name_kaz": "метр" },
{ "id": 5, "name": "грамм", "name_kaz": "грамм" },
{ "id": 37, "name": "м2", "name_kaz": "м2" },
{ "id": 38, "name": "сутки", "name_kaz": "тәулік" },
{ "id": 43, "name": "упак.", "name_kaz": "жиынтық" },
{ "id": 44, "name": "час", "name_kaz": "сағат" }
]
}
| Поле | Тип | Описание |
|---|---|---|
| id | integer | ID единицы измерения (используется в unit_id при создании товара) |
| name | string | Название на русском языке |
| name_kaz | string | Название на казахском языке |
Список товаров каталога с поиском и фильтрацией.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
| page | integer | 1 | Номер страницы |
| per_page | integer | 50 | Записей на странице (1-200) |
| search | string | — | Поиск по названию |
| barcode | string | — | Фильтр по штрихкоду |
| first_char | string | — | Фильтр по первой букве |
{
"data": [
{
"id": 1,
"kaspi_item_id": 12345,
"name": "Coffee Latte",
"unit_id": 1,
"selling_price": 1800,
"image_url": "https://cdn.kaspi.kz/image.jpg",
"barcode": "4870000123456",
"status": "active",
"synced_at": "2026-02-12T10:00:00+05:00"
}
],
"meta": {
"current_page": 1,
"total": 50,
"per_page": 50
}
}
| Поле | Тип | Описание |
|---|---|---|
| id | integer | Внутренний ID товара в ApiPay |
| kaspi_item_id | integer | ID товара в Kaspi (назначается после синхронизации) |
| name | string | Название товара |
| unit_id | integer | ID единицы измерения (см. GET /catalog/units) |
| selling_price | number | Цена продажи |
| image_url | string|null | URL изображения товара |
| barcode | string|null | Штрихкод (EAN-13) |
| status | string | Статус: active, pending, failed, deleting, deleted |
| synced_at | string|null | Дата последней синхронизации с Kaspi |
Загрузить изображение для товара. Формат: multipart/form-data. Макс 10 МБ (jpg, png, gif, webp). Изображения оптимизируются (512x512 PNG) и дедуплицируются по MD5.
{
"image_id": "abc12345-def6-7890-ghij-klmnopqrstuv"
}
Используйте image_id при создании или обновлении товара.
Создать один или несколько товаров (batch, 1-50 штук).
{
"items": [
{
"name": "Coffee Latte",
"selling_price": 1800,
"unit_id": 1,
"image_id": "abc12345-def6-7890-ghij-klmnopqrstuv"
}
]
}
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| name | string | Да | Название товара, макс 255 |
| selling_price | number | Да | Цена, мин 0.01 |
| unit_id | integer | Да | ID единицы измерения (из GET /catalog/units) |
| image_id | string (uuid) | Нет | ID изображения из upload-image |
{
"data": [
{
"id": 101,
"name": "Coffee Latte",
"selling_price": 1800,
"unit_id": 1,
"barcode": null,
"status": "pending"
}
]
}
pending и синхронизируются с Kaspi в фоне. Используйте GET /catalog для проверки финального статуса (active после успешной синхронизации).
Обновить товар каталога. Все поля опциональны — обновляются только переданные.
{
"name": "Updated Name",
"selling_price": 2000,
"unit_id": 1,
"image_id": "abc12345-def6-7890-ghij-klmnopqrstuv",
"is_image_deleted": false
}
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| name | string | Нет | Новое название, макс 255 |
| selling_price | number | Нет | Новая цена, мин 0.01 |
| unit_id | integer | Нет | ID единицы измерения (из GET /catalog/units) |
| image_id | string (uuid) | Нет | Новое изображение из upload-image |
| is_image_deleted | boolean | Нет | Удалить текущее изображение |
{
"message": "Catalog item update queued",
"catalog_item_id": 1
}
Удалить товар каталога.
{
"message": "Catalog item deletion queued",
"catalog_item_id": 1
}
Автоматическое создание повторяющихся счетов по расписанию. Поддержка grace period и retry при неоплате.
Создать подписку. Два режима: без корзины (передать amount) или с корзиной (передать cart_items, сумма рассчитывается автоматически). Для организаций с каталогом cart_items обязателен.
{
"phone_number": "87001234567",
"amount": 5000,
"billing_period": "monthly",
"billing_day": 15,
"description": "Ежемесячная подписка",
"subscriber_name": "Иван Иванов",
"external_subscriber_id": "cust-123",
"started_at": "2026-03-01",
"max_retry_attempts": 3,
"retry_interval_hours": 24,
"grace_period_days": 7,
"metadata": { "plan": "premium" }
}
{
"phone_number": "87001234567",
"billing_period": "monthly",
"billing_day": 15,
"description": "Ежемесячная подписка",
"subscriber_name": "Иван Иванов",
"cart_items": [
{ "catalog_item_id": 1, "count": 2 },
{ "catalog_item_id": 5, "count": 1 }
]
}
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| phone_number | string | Да | Формат: 8XXXXXXXXXX |
| amount | number | Да (без корзины) | 100 - 1 000 000. Игнорируется при cart_items. |
| billing_period | string | Да | daily, weekly, biweekly, monthly, quarterly, yearly |
| billing_day | integer | Нет | День периода (1-28) |
| description | string | Нет | Макс 255 |
| subscriber_name | string | Нет | Имя подписчика, макс 255 |
| external_subscriber_id | string | Нет | Ваш ID подписчика, макс 255 |
| started_at | date | Нет | Дата начала (по умолчанию сегодня) |
| max_retry_attempts | integer | Нет | Макс попыток повтора (1-10) |
| retry_interval_hours | integer | Нет | Интервал между попытками, часы (1-168) |
| grace_period_days | integer | Нет | Льготный период, дни (1-30) |
| metadata | object | Нет | Произвольные данные JSON |
| cart_items | array | Нет | Товары корзины (только для организаций с каталогом). При передаче amount игнорируется. |
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| catalog_item_id | integer | Да | ID товара из каталога (поле id из GET /catalog) |
| count | integer | Да | Количество, мин 1 |
GET /catalog, чтобы использовать корректные catalog_item_id. При каждом биллинге цены пересчитываются из актуального каталога.
{
"message": "Subscription created",
"subscription": {
"id": 1,
"subscriber_name": "Иван Иванов",
"phone_number": "87001234567",
"amount": "5000.00",
"cart_items": null,
"billing_period": "monthly",
"billing_period_label": "Ежемесячно",
"billing_day": 15,
"billing_day_label": "15-го числа",
"description": "Ежемесячная подписка",
"external_subscriber_id": "cust-123",
"status": "active",
"status_label": "Активна",
"status_color": "green",
"started_at": "2026-02-12T00:00:00+05:00",
"next_billing_at": "2026-03-15T00:00:00+05:00",
"next_billing_in_days": 31,
"next_billing_label": "через 31 день",
"paused_at": null,
"cancelled_at": null,
"failed_attempts": 0,
"max_retry_attempts": 3,
"retry_interval_hours": 24,
"grace_period_days": 7,
"in_grace_period": false,
"is_sandbox": false,
"metadata": { "plan": "premium" },
"created_at": "2026-02-12T12:00:00+05:00",
"updated_at": "2026-02-12T12:00:00+05:00"
}
}
Список подписок с фильтрацией и пагинацией.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
| page | integer | 1 | Номер страницы |
| per_page | integer | 10 | Записей на странице (1-100) |
| status | string | — | Фильтр: active, paused, cancelled, expired |
| phone_number | string | — | Фильтр по номеру телефона |
| external_subscriber_id | string | — | Фильтр по вашему ID подписчика |
{
"current_page": 1,
"data": [
{
"id": 1,
"subscriber_name": "Иван Иванов",
"phone_number": "87001234567",
"amount": "5000.00",
"cart_items": null,
"billing_period": "monthly",
"billing_period_label": "Ежемесячно",
"billing_day": 15,
"billing_day_label": "15-го числа",
"description": "Ежемесячная подписка",
"external_subscriber_id": "cust-123",
"status": "active",
"status_label": "Активна",
"status_color": "green",
"started_at": "2026-02-01T00:00:00+05:00",
"next_billing_at": "2026-03-15T00:00:00+05:00",
"next_billing_in_days": 17,
"next_billing_label": "через 17 дней",
"paused_at": null,
"cancelled_at": null,
"failed_attempts": 0,
"max_retry_attempts": 3,
"retry_interval_hours": 24,
"grace_period_days": 7,
"in_grace_period": false,
"is_sandbox": false,
"metadata": null,
"created_at": "2026-02-12T12:00:00+05:00",
"updated_at": "2026-02-12T12:00:00+05:00"
}
],
"total": 10
}
Получить подписку со статистикой и информацией о последнем платеже.
{
"subscription": {
"id": 1,
"subscriber_name": "Иван Иванов",
"phone_number": "87001234567",
"amount": "5000.00",
"cart_items": null,
"billing_period": "monthly",
"billing_period_label": "Ежемесячно",
"billing_day": 15,
"billing_day_label": "15-го числа",
"description": "Ежемесячная подписка",
"status": "active",
"status_label": "Активна",
"status_color": "green",
"external_subscriber_id": "cust-123",
"started_at": "2026-02-01T00:00:00+05:00",
"next_billing_at": "2026-03-15T00:00:00+05:00",
"next_billing_in_days": 17,
"next_billing_label": "через 17 дней",
"paused_at": null,
"cancelled_at": null,
"max_retry_attempts": 3,
"retry_interval_hours": 24,
"grace_period_days": 7,
"failed_attempts": 0,
"in_grace_period": false,
"is_sandbox": false,
"metadata": { "plan": "premium" },
"stats": {
"total_payments": 5,
"successful_payments": 4,
"failed_payments": 1,
"total_amount": "20000.00"
},
"last_payment": {
"id": 200,
"status": "paid",
"amount": "5000.00",
"paid_at": "2026-02-15T12:00:00+05:00"
},
"created_at": "2026-02-01T12:00:00+05:00",
"updated_at": "2026-02-15T12:00:00+05:00"
}
}
| Поле | Тип | Описание |
|---|---|---|
| id | integer | ID подписки |
| subscriber_name | string|null | Имя подписчика |
| phone_number | string | Телефон подписчика |
| amount | string | Сумма списания |
| billing_period | string | Период: daily, weekly, biweekly, monthly, quarterly, yearly |
| billing_day | integer|null | День списания |
| status | string | Статус подписки |
| next_billing_at | string|null | Дата следующего списания |
| failed_attempts | integer | Количество неудачных попыток оплаты |
| in_grace_period | boolean | Находится ли в льготном периоде |
| stats | object | Статистика: total_payments, successful_payments, failed_payments, total_amount |
| last_payment | object|null | Последний платёж подписки |
| metadata | object|null | Произвольные данные |
Обновить подписку. Все поля опциональны — обновляются только переданные. При передаче cart_items сумма пересчитывается автоматически.
{
"amount": 7000,
"billing_day": 20,
"description": "Обновлённая подписка",
"subscriber_name": "Иван Иванов",
"max_retry_attempts": 5,
"retry_interval_hours": 12,
"grace_period_days": 14,
"metadata": { "plan": "enterprise" }
}
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| amount | number | Нет | Новая сумма (100 - 1 000 000) |
| billing_day | integer | Нет | День периода (1-28) |
| description | string | Нет | Описание, макс 255 |
| subscriber_name | string | Нет | Имя подписчика, макс 255 |
| max_retry_attempts | integer | Нет | Макс попыток повтора (1-10) |
| retry_interval_hours | integer | Нет | Интервал между попытками (1-168) |
| grace_period_days | integer | Нет | Льготный период, дни (1-30) |
| metadata | object | Нет | Произвольные данные JSON |
| cart_items | array | Нет | Новая корзина товаров (формат как при создании). Сумма пересчитывается автоматически. |
{
"message": "Subscription updated",
"subscription": {
"id": 1,
"subscriber_name": "Иван Иванов",
"phone_number": "87001234567",
"amount": "7000.00",
"cart_items": null,
"billing_period": "monthly",
"billing_period_label": "Ежемесячно",
"billing_day": 20,
"billing_day_label": "20-го числа",
"description": "Обновлённая подписка",
"status": "active",
"status_label": "Активна",
"status_color": "green",
"next_billing_at": "2026-03-20T00:00:00+05:00",
"next_billing_in_days": 22,
"paused_at": null,
"cancelled_at": null,
"failed_attempts": 0,
"max_retry_attempts": 5,
"retry_interval_hours": 12,
"grace_period_days": 14,
"in_grace_period": false,
"is_sandbox": false,
"metadata": { "plan": "enterprise" },
"created_at": "2026-02-12T12:00:00+05:00",
"updated_at": "2026-02-26T10:00:00+05:00"
}
}
Поставить подписку на паузу. Только для статуса active.
{
"message": "Subscription paused",
"subscription": {
"id": 1,
"subscriber_name": "Иван Иванов",
"phone_number": "87001234567",
"amount": "5000.00",
"billing_period": "monthly",
"billing_day": 15,
"status": "paused",
"status_label": "На паузе",
"status_color": "orange",
"next_billing_at": null,
"paused_at": "2026-02-26T10:00:00+05:00",
"cancelled_at": null,
"updated_at": "2026-02-26T10:00:00+05:00"
}
}
Возобновить подписку после паузы. Только для статуса paused.
{
"message": "Subscription resumed",
"subscription": {
"id": 1,
"subscriber_name": "Иван Иванов",
"phone_number": "87001234567",
"amount": "5000.00",
"billing_period": "monthly",
"billing_day": 15,
"status": "active",
"status_label": "Активна",
"status_color": "green",
"next_billing_at": "2026-03-15T00:00:00+05:00",
"paused_at": null,
"cancelled_at": null,
"updated_at": "2026-02-26T10:00:00+05:00"
}
}
Отменить подписку навсегда. Нельзя восстановить.
{
"message": "Subscription cancelled",
"subscription": {
"id": 1,
"subscriber_name": "Иван Иванов",
"phone_number": "87001234567",
"amount": "5000.00",
"billing_period": "monthly",
"billing_day": 15,
"status": "cancelled",
"status_label": "Отменена",
"status_color": "red",
"next_billing_at": null,
"paused_at": null,
"cancelled_at": "2026-02-26T10:00:00+05:00",
"updated_at": "2026-02-26T10:00:00+05:00"
}
}
Список счетов, созданных подпиской.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
| page | integer | 1 | Номер страницы |
| per_page | integer | 10 | Записей на странице (1-100) |
{
"data": [
{
"id": 1,
"invoice_id": 200,
"billing_period_start": "2026-02-01",
"billing_period_end": "2026-02-28",
"billing_period_label": "01.02.2026 — 28.02.2026",
"amount": "5000.00",
"attempt_number": 1,
"status": "paid",
"status_label": "Оплачен",
"status_color": "green",
"paid_at": "2026-02-15T12:00:00+05:00",
"failure_reason": null,
"invoice": {
"id": 200,
"kaspi_invoice_id": "13234689513",
"status": "paid"
},
"created_at": "2026-02-15T00:00:00+05:00"
}
],
"meta": {
"current_page": 1,
"total": 5,
"per_page": 20
}
}
subscription_invoice (попытка биллинга), а не обычный счёт. Содержит период биллинга, номер попытки и вложенный объект invoice с данными реального счёта Kaspi.
| Статус | Описание |
|---|---|
active | Списания по расписанию |
paused | Временно приостановлена |
cancelled | Отменена навсегда |
expired | Истекла (после grace period) |
| Период | Описание |
|---|---|
daily | Каждый день |
weekly | Каждую неделю |
biweekly | Каждые 2 недели |
monthly | Каждый месяц |
quarterly | Каждые 3 месяца |
yearly | Каждый год |
Если клиент не оплатил счёт и все retry-попытки исчерпаны:
in_grace_period: true)grace_period_days — подписка продолжает работатьexpiredПолные и частичные возвраты по оплаченным счетам.
Список всех возвратов с фильтрацией и пагинацией.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
| page | integer | 1 | Номер страницы |
| per_page | integer | 10 | Записей на странице (1-100) |
| status[] | array | — | Фильтр: pending, processing, completed, failed |
| invoice_id | integer | — | Фильтр по ID счёта |
| date_from | string | — | Дата от (YYYY-MM-DD) |
| date_to | string | — | Дата до (YYYY-MM-DD) |
{
"current_page": 1,
"data": [
{
"id": 456,
"invoice_id": 124,
"amount": "2000.00",
"reason": "Запрос клиента",
"status": "completed",
"created_at": "2026-02-12T11:00:00+05:00"
}
],
"total": 3
}
| Поле | Тип | Описание |
|---|---|---|
| id | integer | ID возврата |
| invoice_id | integer | ID счёта, по которому сделан возврат |
| amount | string | Сумма возврата |
| reason | string|null | Причина возврата |
| status | string | Статус: pending, processing, completed, failed |
| created_at | string | Дата создания |
| Статус | Описание |
|---|---|
pending | Создан, в очереди на обработку |
processing | Обрабатывается Kaspi |
completed | Успешно возвращён |
failed | Ошибка возврата |
| Событие | Описание |
|---|---|
invoice.status_changed | Изменение статуса счёта (paid, cancelled, expired) |
invoice.refunded | Создан возврат по оплаченному счёту |
subscription.payment_succeeded | Успешный платёж по подписке |
subscription.payment_failed | Неудачный платёж по подписке |
subscription.grace_period_started | Начался льготный период подписки |
subscription.expired | Подписка истекла (после grace period) |
webhook.test | Тестовый webhook |
{
"event": "invoice.status_changed",
"invoice": {
"id": 42,
"external_order_id": "order_123",
"amount": "15000.00",
"subtotal": "16500.00",
"discount_sum": "1500.00",
"discount_percentage": "10",
"status": "paid",
"description": "Оплата заказа",
"kaspi_invoice_id": "13234689513",
"client_name": "Иван Иванов",
"client_phone": "87071234567",
"paid_at": "2026-02-12T14:35:00+05:00"
},
"source": "My API Key",
"timestamp": "2026-02-12T14:35:01+05:00"
}
{
"event": "invoice.refunded",
"refund": {
"id": 5,
"amount": "2000.00",
"status": "completed",
"reason": "Возврат товара",
"created_at": "2026-02-12T10:00:00+05:00"
},
"invoice": {
"id": 42,
"external_order_id": "order_123",
"amount": "5000.00",
"subtotal": "5500.00",
"discount_sum": "500.00",
"total_refunded": "2000.00",
"available_for_refund": "3000.00",
"is_fully_refunded": false,
"status": "paid",
"kaspi_invoice_id": "13234689513"
},
"source": "My API Key",
"timestamp": "2026-02-12T10:00:01+05:00"
}
subtotal, discount_sum и discount_percentage появляются в webhook payloads только при наличии скидки в счёте.
{
"event": "subscription.payment_succeeded",
"subscription": {
"id": 10,
"external_subscriber_id": "CLIENT-001",
"phone_number": "87071234567",
"subscriber_name": "Иван Иванов",
"amount": "5000.00",
"billing_period": "monthly",
"status": "active",
"next_billing_at": "2026-03-01T00:00:00+05:00",
"failed_attempts": 0,
"in_grace_period": false
},
"invoice_id": 200,
"amount": "5000.00",
"paid_at": "2026-02-01T12:00:00+05:00",
"source": "My API Key",
"timestamp": "2026-02-01T12:00:01+05:00"
}
{
"event": "subscription.payment_failed",
"subscription": {
"id": 10,
"phone_number": "87071234567",
"amount": "5000.00",
"billing_period": "monthly",
"status": "active",
"failed_attempts": 2,
"in_grace_period": false
},
"invoice_id": 201,
"amount": "5000.00",
"reason": "Invoice expired",
"attempt_number": 2,
"source": "My API Key",
"timestamp": "2026-02-02T12:00:01+05:00"
}
{
"event": "subscription.grace_period_started",
"subscription": {
"id": 10,
"phone_number": "87071234567",
"amount": "5000.00",
"status": "active",
"failed_attempts": 3,
"in_grace_period": true
},
"grace_period_days": 3,
"expires_at": "2026-02-05T12:00:00+05:00",
"source": "My API Key",
"timestamp": "2026-02-02T12:00:01+05:00"
}
{
"event": "subscription.expired",
"subscription": {
"id": 10,
"phone_number": "87071234567",
"amount": "5000.00",
"status": "expired",
"next_billing_at": null,
"failed_attempts": 3,
"in_grace_period": false
},
"source": "My API Key",
"timestamp": "2026-02-05T12:00:01+05:00"
}
sourceВсе webhook payload содержат поле source — имя API-ключа, создавшего ресурс (счёт или подписку). Может быть null.
Заголовок: X-Webhook-Signature: sha256=<HMAC-SHA256>
const crypto = require('crypto')
function verifyWebhook(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
}
function verifyWebhook($payload, $signature, $secret) {
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}
import hmac, hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
const response = await fetch('https://bpapi.bazarbay.site/api/v1/invoices', {
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 10000,
phone_number: '87001234567',
description: 'Payment for order #123'
})
})
const data = await response.json()
console.log('Invoice created:', data.id)
import requests
response = requests.post(
'https://bpapi.bazarbay.site/api/v1/invoices',
headers={'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},
json={'amount': 10000, 'phone_number': '87001234567'}
)
data = response.json()
print(f"Invoice created: {data['id']}")
$ch = curl_init('https://bpapi.bazarbay.site/api/v1/invoices');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-API-Key: YOUR_API_KEY', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['amount' => 10000, 'phone_number' => '87001234567']),
CURLOPT_RETURNTRANSFER => true
]);
$response = json_decode(curl_exec($ch), true);
echo "Invoice created: " . $response['id'];
curl -X POST https://bpapi.bazarbay.site/api/v1/invoices \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"amount": 10000, "phone_number": "87001234567"}'
const response = await fetch('https://bpapi.bazarbay.site/api/v1/invoices', {
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone_number: '87001234567',
description: 'Cart order',
cart_items: [
{ catalog_item_id: 1, count: 2 },
{ catalog_item_id: 5, count: 3 }
]
})
})
curl -X POST https://bpapi.bazarbay.site/api/v1/subscriptions \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "87001234567",
"amount": 5000,
"billing_period": "monthly",
"description": "Monthly subscription"
}'
const response = await fetch('https://bpapi.bazarbay.site/api/v1/subscriptions', {
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone_number: '87001234567',
amount: 5000,
billing_period: 'monthly',
description: 'Monthly subscription'
})
})
const data = await response.json()
console.log('Subscription:', data.subscription.id)
# Шаг 1: Загрузить изображение
IMAGE_ID=$(curl -s -X POST https://bpapi.bazarbay.site/api/v1/catalog/upload-image \
-H "X-API-Key: YOUR_API_KEY" \
-F "image=@product.jpg" | jq -r '.image_id')
# Шаг 2: Создать товар с изображением
curl -X POST https://bpapi.bazarbay.site/api/v1/catalog \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"items\": [{
\"name\": \"Coffee Latte\",
\"selling_price\": 1800,
\"unit_id\": 1,
\"image_id\": \"$IMAGE_ID\"
}]
}"
# Полный возврат
curl -X POST https://bpapi.bazarbay.site/api/v1/invoices/42/refund \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Customer request"}'
# Частичный возврат
curl -X POST https://bpapi.bazarbay.site/api/v1/invoices/42/refund \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"amount": 5000, "reason": "Partial return"}'
| Код | Описание |
|---|---|
| 400 | Bad Request — некорректный запрос или состояние |
| 401 | Unauthorized — неверный, отсутствующий или истёкший API ключ |
| 403 | Forbidden — организация не верифицирована |
| 404 | Not Found — ресурс не найден |
| 422 | Validation Error — ошибка валидации полей |
| 429 | Too Many Requests — превышен rate limit (проверьте retry_after) |
| 500 | Server Error — ошибка сервера |
| 502 | Bad Gateway — ошибка Kaspi API |
| 503 | Service Unavailable — Kaspi сессия истекла |
{
"message": "Описание ошибки",
"errors": {
"field_name": ["детали ошибки"]
}
}