Полная документация 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 req/min |
| Content-Type | application/json |
| # | Метод | Путь | Описание |
|---|---|---|---|
| Счета (Invoices) | |||
| 1 | POST | /invoices | Создать счёт |
| 2 | GET | /invoices | Список счетов |
| 3 | GET | /invoices/{id} | Просмотр счёта |
| 4 | POST | /invoices/{id}/cancel | Отменить счёт |
| 5 | POST | /invoices/status/check | Проверить статусы счетов |
| Возвраты (Refunds) | |||
| 6 | POST | /invoices/{id}/refund | Возврат по счёту |
| 7 | GET | /invoices/{id}/refunds | Список возвратов по счёту |
| 8 | GET | /refunds | Список возвратов |
| Каталог (Catalog) | |||
| 9 | GET | /catalog/units | Список единиц измерения |
| 10 | GET | /catalog | Список товаров каталога |
| 11 | POST | /catalog/upload-image | Загрузить изображение для каталога |
| 12 | POST | /catalog | Создать товар каталога |
| 13 | PATCH | /catalog/{id} | Обновить товар каталога |
| 14 | DELETE | /catalog/{id} | Удалить товар каталога |
| Подписки (Subscriptions) | |||
| 15 | POST | /subscriptions | Создать подписку |
| 16 | GET | /subscriptions | Список подписок |
| 17 | GET | /subscriptions/{id} | Просмотр подписки |
| 18 | PUT | /subscriptions/{id} | Обновить подписку |
| 19 | POST | /subscriptions/{id}/pause | Приостановить подписку |
| 20 | POST | /subscriptions/{id}/resume | Возобновить подписку |
| 21 | POST | /subscriptions/{id}/cancel | Отменить подписку |
| 22 | GET | /subscriptions/{id}/invoices | История платежей подписки |
Возвращает пагинированный список счетов с возможностью фильтрации.
Создаёт новый счёт для оплаты через Kaspi Pay. Для организаций с каталогом используйте `cart_items` вместо `amount`.
Возвращает детальную информацию о счёте, включая товары.
Отменяет счёт в статусе pending или processing. Для production счетов отмена выполняется асинхронно (статус 202).
Создаёт возврат по оплаченному счёту. Поддерживает полный и частичный возврат, а также поэлементный возврат через `return_items`.
Возвращает все возвраты по конкретному счёту.
Диспатчит задачи проверки статусов для указанных счетов через Kaspi API.
Возвращает список возвратов организации с фильтрацией и пагинацией.
Возвращает список доступных единиц измерения для товаров каталога (шт, кг, л и т.д.).
Возвращает пагинированный список товаров каталога организации. Поддерживает поиск и фильтрацию.
Создаёт один или несколько товаров в каталоге организации. Товары сохраняются локально со статусом pending и отправляются в Kaspi API через очередь.
Загружает и оптимизирует изображение товара. Использует MD5 дедупликацию — повторная загрузка того же изображения вернёт существующий image_id.
Обновляет данные товара каталога. Локальные поля обновляются сразу, изменения в Kaspi API отправляются через очередь.
Помечает товар как удаляемый и отправляет запрос на удаление в Kaspi API через очередь.
Возвращает пагинированный список подписок с возможностью фильтрации.
Создаёт новую рекуррентную подписку. Первый счёт выставляется автоматически.
Возвращает детальную информацию о подписке, включая статистику и последний платёж.
Обновляет параметры существующей подписки. Все поля опциональны.
Приостанавливает активную подписку. Новые счета не будут выставляться до возобновления.
Возобновляет приостановленную подписку. Следующий биллинг будет рассчитан автоматически.
Безвозвратно отменяет подписку. Новые счета больше не будут выставляться.
Возвращает пагинированный список всех платежей (счетов) по подписке.
Webhooks настраиваются через личный кабинет ApiPay.kz (Настройки > Подключение). При создании webhook вы получите secret для верификации подписи (HMAC-SHA256).
invoice.status_changedinvoice.refundedsubscription.payment_succeededsubscription.payment_failedsubscription.grace_period_startedsubscription.expiredwebhook.testinvoice.status_changed{
"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",
"is_sandbox": false,
"paid_at": "2026-02-12T14:35:00+05:00"
},
"source": "My API Key",
"timestamp": "2026-02-12T14:35:01+05:00"
}
invoice.refunded{
"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,
"is_sandbox": false,
"status": "paid",
"kaspi_invoice_id": "13234689513"
},
"source": "My API Key",
"timestamp": "2026-02-12T10:00:01+05:00"
}
subscription.payment_succeeded{
"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,
"is_sandbox": 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"
}
subscription.payment_failed{
"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,
"is_sandbox": 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"
}
subscription.grace_period_started{
"event": "subscription.grace_period_started",
"subscription": {
"id": 10,
"phone_number": "87071234567",
"amount": "5000.00",
"status": "active",
"failed_attempts": 3,
"in_grace_period": true,
"is_sandbox": false
},
"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"
}
subscription.expired{
"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,
"is_sandbox": false
},
"source": "My API Key",
"timestamp": "2026-02-05T12:00:01+05:00"
}
Header: X-Webhook-Signature: sha256=<hex>
import hmac
import 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)
# Usage:
# signature = request.headers.get('X-Webhook-Signature')
# is_valid = verify_webhook(request.body, signature, webhook_secret)
import crypto from '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)
)
}
// Usage:
// const signature = req.headers['x-webhook-signature']
// const isValid = verifyWebhook(req.rawBody, signature, webhookSecret)
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',
'description': 'Payment for order #123'
}
)
data = response.json()
print(f"Invoice created: {data['id']}")
true,
CURLOPT_HTTPHEADER => [
'X-API-Key: YOUR_API_KEY',
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 10000,
'phone_number' => '87001234567',
'description' => 'Payment for order #123'
]),
CURLOPT_RETURNTRANSFER => true
]);
$response = json_decode(curl_exec($ch), true);
echo "Invoice created: " . $response['id'];
curl_close($ch);
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",
"description": "Payment for order #123"
}'
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, price: 4500.00 },
{ catalog_item_id: 5, count: 3 }
],
discount_percentage: 10
})
})
// Response includes subtotal, discount_sum, discount_percentage
import requests
response = requests.post(
'https://bpapi.bazarbay.site/api/v1/invoices',
headers={
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
json={
'phone_number': '87001234567',
'description': 'Cart order',
'cart_items': [
{'catalog_item_id': 1, 'count': 2, 'price': 4500.00},
{'catalog_item_id': 5, 'count': 3}
],
'discount_percentage': 10
}
)
data = response.json()
true,
CURLOPT_HTTPHEADER => [
'X-API-Key: YOUR_API_KEY',
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'phone_number' => '87001234567',
'description' => 'Cart order',
'cart_items' => [
['catalog_item_id' => 1, 'count' => 2, 'price' => 4500.00],
['catalog_item_id' => 5, 'count' => 3]
],
'discount_percentage' => 10
]),
CURLOPT_RETURNTRANSFER => true
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
curl -X POST https://bpapi.bazarbay.site/api/v1/invoices \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "87001234567",
"description": "Cart order",
"cart_items": [
{ "catalog_item_id": 1, "count": 2, "price": 4500.00 },
{ "catalog_item_id": 5, "count": 3 }
],
"discount_percentage": 10
}'
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)
import requests
response = requests.post(
'https://bpapi.bazarbay.site/api/v1/subscriptions',
headers={
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
json={
'phone_number': '87001234567',
'amount': 5000,
'billing_period': 'monthly',
'description': 'Monthly subscription'
}
)
data = response.json()
print(f"Subscription: {data['subscription']['id']}")
true,
CURLOPT_HTTPHEADER => [
'X-API-Key: YOUR_API_KEY',
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'phone_number' => '87001234567',
'amount' => 5000,
'billing_period' => 'monthly',
'description' => 'Monthly subscription'
]),
CURLOPT_RETURNTRANSFER => true
]);
$response = json_decode(curl_exec($ch), true);
echo "Subscription: " . $response['subscription']['id'];
curl_close($ch);
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',
billing_period: 'monthly',
description: 'Monthly cart subscription',
cart_items: [
{ catalog_item_id: 1, count: 2 },
{ catalog_item_id: 5, count: 1 }
]
})
})
const data = await response.json()
import requests
response = requests.post(
'https://bpapi.bazarbay.site/api/v1/subscriptions',
headers={
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
json={
'phone_number': '87001234567',
'billing_period': 'monthly',
'description': 'Monthly cart subscription',
'cart_items': [
{'catalog_item_id': 1, 'count': 2},
{'catalog_item_id': 5, 'count': 1}
]
}
)
data = response.json()
true,
CURLOPT_HTTPHEADER => [
'X-API-Key: YOUR_API_KEY',
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'phone_number' => '87001234567',
'billing_period' => 'monthly',
'description' => 'Monthly cart subscription',
'cart_items' => [
['catalog_item_id' => 1, 'count' => 2],
['catalog_item_id' => 5, 'count' => 1]
]
]),
CURLOPT_RETURNTRANSFER => true
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
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",
"billing_period": "monthly",
"description": "Monthly cart subscription",
"cart_items": [
{ "catalog_item_id": 1, "count": 2 },
{ "catalog_item_id": 5, "count": 1 }
]
}'
// Step 1: Upload image
const formData = new FormData()
formData.append('image', imageFile)
const uploadRes = await fetch('https://bpapi.bazarbay.site/api/v1/catalog/upload-image', {
method: 'POST',
headers: { 'X-API-Key': 'YOUR_API_KEY' },
body: formData
})
const { image_id } = await uploadRes.json()
// Step 2: Create product with image
const productRes = await fetch('https://bpapi.bazarbay.site/api/v1/catalog', {
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
items: [{
name: 'Coffee Latte',
selling_price: 1800,
unit_id: 1,
image_id
}]
})
})
import requests
# Step 1: Upload image
upload_res = requests.post(
'https://bpapi.bazarbay.site/api/v1/catalog/upload-image',
headers={'X-API-Key': 'YOUR_API_KEY'},
files={'image': open('product.jpg', 'rb')}
)
image_id = upload_res.json()['image_id']
# Step 2: Create product with image
product_res = requests.post(
'https://bpapi.bazarbay.site/api/v1/catalog',
headers={
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
json={
'items': [{
'name': 'Coffee Latte',
'selling_price': 1800,
'unit_id': 1,
'image_id': image_id
}]
}
)
true,
CURLOPT_HTTPHEADER => ['X-API-Key: YOUR_API_KEY'],
CURLOPT_POSTFIELDS => ['image' => $cfile],
CURLOPT_RETURNTRANSFER => true
]);
$imageId = json_decode(curl_exec($ch), true)['image_id'];
curl_close($ch);
// Step 2: Create product with image
$ch = curl_init('https://bpapi.bazarbay.site/api/v1/catalog');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-API-Key: YOUR_API_KEY',
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'items' => [[
'name' => 'Coffee Latte',
'selling_price' => 1800,
'unit_id' => 1,
'image_id' => $imageId
]]
]),
CURLOPT_RETURNTRANSFER => true
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
# Step 1: Upload image
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')
# Step 2: Create product with image
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\"
}]
}"
// Full refund
await fetch('https://bpapi.bazarbay.site/api/v1/invoices/42/refund', {
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({ reason: 'Customer request' })
})
// Partial refund
await fetch('https://bpapi.bazarbay.site/api/v1/invoices/42/refund', {
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({ amount: 5000, reason: 'Partial return' })
})
import requests
# Full refund
requests.post(
'https://bpapi.bazarbay.site/api/v1/invoices/42/refund',
headers={
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
json={'reason': 'Customer request'}
)
# Partial refund
requests.post(
'https://bpapi.bazarbay.site/api/v1/invoices/42/refund',
headers={
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
json={'amount': 5000, 'reason': 'Partial return'}
)
true,
CURLOPT_HTTPHEADER => [
'X-API-Key: YOUR_API_KEY',
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'reason' => 'Customer request'
]),
CURLOPT_RETURNTRANSFER => true
]);
curl_exec($ch);
curl_close($ch);
// Partial refund
$ch = curl_init('https://bpapi.bazarbay.site/api/v1/invoices/42/refund');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-API-Key: YOUR_API_KEY',
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 5000,
'reason' => 'Partial return'
]),
CURLOPT_RETURNTRANSFER => true
]);
curl_exec($ch);
curl_close($ch);
# Full refund
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"}'
# Partial refund
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": ["детали ошибки"]
}
}