ApiPay API v2 Documentation — ApiPay.kz

Полная документация ApiPay REST API v2 — Интернет-эквайринг через Kaspi Pay (Phone Payments)

REST API для приёма платежей по номеру телефона через Kaspi Pay. Без договора с банком, без комиссий. Поддержка каталога, подписок, возвратов и webhooks.

Быстрый старт

Песочница доступна сразу! При регистрации создаётся sandbox-организация для тестирования API. Вы можете создавать тестовые счета (is_sandbox=true) до подключения Kaspi Business. Переключение в production — через личный кабинет.
  1. Войдите в личный кабинет apipay.kz/login через WhatsApp
  2. Получите API ключ в Настройки → Подключение
  3. Создавайте счета: POST /api/v1/invoices
  4. Для работы с реальными платежами — напишите в WhatsApp поддержки, мы подключим ваш Kaspi Business с правами Кассира
  5. Настройте webhook в личном кабинете для получения уведомлений об оплате

Базовая конфигурация

ПараметрЗначение
Base URLhttps://bpapi.bazarbay.site/api/v1
АутентификацияHeader X-API-Key: ваш_api_ключ
Rate Limit60 req/min
Content-Typeapplication/json

Обзор эндпоинтов

#МетодПутьОписание
Счета (Invoices)
1POST/invoicesСоздать счёт
2GET/invoicesСписок счетов
3GET/invoices/{id}Просмотр счёта
4POST/invoices/{id}/cancelОтменить счёт
5POST/invoices/status/checkПроверить статусы счетов
Возвраты (Refunds)
6POST/invoices/{id}/refundВозврат по счёту
7GET/invoices/{id}/refundsСписок возвратов по счёту
8GET/refundsСписок возвратов
Каталог (Catalog)
9GET/catalog/unitsСписок единиц измерения
10GET/catalogСписок товаров каталога
11POST/catalog/upload-imageЗагрузить изображение для каталога
12POST/catalogСоздать товар каталога
13PATCH/catalog/{id}Обновить товар каталога
14DELETE/catalog/{id}Удалить товар каталога
Подписки (Subscriptions)
15POST/subscriptionsСоздать подписку
16GET/subscriptionsСписок подписок
17GET/subscriptions/{id}Просмотр подписки
18PUT/subscriptions/{id}Обновить подписку
19POST/subscriptions/{id}/pauseПриостановить подписку
20POST/subscriptions/{id}/resumeВозобновить подписку
21POST/subscriptions/{id}/cancelОтменить подписку
22GET/subscriptions/{id}/invoicesИстория платежей подписки

Health Check


Счета (Invoices)

GET /invoices

Возвращает пагинированный список счетов с возможностью фильтрации.

POST /invoices

Создаёт новый счёт для оплаты через Kaspi Pay. Для организаций с каталогом используйте `cart_items` вместо `amount`.

GET /invoices/{id}

Возвращает детальную информацию о счёте, включая товары.

POST /invoices/{id}/cancel

Отменяет счёт в статусе pending или processing. Для production счетов отмена выполняется асинхронно (статус 202).

POST /invoices/{id}/refund

Создаёт возврат по оплаченному счёту. Поддерживает полный и частичный возврат, а также поэлементный возврат через `return_items`.

GET /invoices/{id}/refunds

Возвращает все возвраты по конкретному счёту.

POST /invoices/status/check

Диспатчит задачи проверки статусов для указанных счетов через Kaspi API.


Возвраты (Refunds)

GET /refunds

Возвращает список возвратов организации с фильтрацией и пагинацией.


Каталог (Catalog)

GET /catalog/units

Возвращает список доступных единиц измерения для товаров каталога (шт, кг, л и т.д.).

GET /catalog

Возвращает пагинированный список товаров каталога организации. Поддерживает поиск и фильтрацию.

POST /catalog

Создаёт один или несколько товаров в каталоге организации. Товары сохраняются локально со статусом pending и отправляются в Kaspi API через очередь.

POST /catalog/upload-image

Загружает и оптимизирует изображение товара. Использует MD5 дедупликацию — повторная загрузка того же изображения вернёт существующий image_id.

PATCH /catalog/{id}

Обновляет данные товара каталога. Локальные поля обновляются сразу, изменения в Kaspi API отправляются через очередь.

DELETE /catalog/{id}

Помечает товар как удаляемый и отправляет запрос на удаление в Kaspi API через очередь.


Подписки (Subscriptions)

GET /subscriptions

Возвращает пагинированный список подписок с возможностью фильтрации.

POST /subscriptions

Создаёт новую рекуррентную подписку. Первый счёт выставляется автоматически.

GET /subscriptions/{id}

Возвращает детальную информацию о подписке, включая статистику и последний платёж.

PUT /subscriptions/{id}

Обновляет параметры существующей подписки. Все поля опциональны.

POST /subscriptions/{id}/pause

Приостанавливает активную подписку. Новые счета не будут выставляться до возобновления.

POST /subscriptions/{id}/resume

Возобновляет приостановленную подписку. Следующий биллинг будет рассчитан автоматически.

POST /subscriptions/{id}/cancel

Безвозвратно отменяет подписку. Новые счета больше не будут выставляться.

GET /subscriptions/{id}/invoices

Возвращает пагинированный список всех платежей (счетов) по подписке.


Webhooks

Webhooks настраиваются через личный кабинет ApiPay.kz (Настройки > Подключение). При создании webhook вы получите secret для верификации подписи (HMAC-SHA256).

События

Примеры payload

invoice.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"
}

Retry Policy

Верификация подписи

Header: X-Webhook-Signature: sha256=<hex>

python

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)

php

javascript

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)

Примеры кода

Создание счёта

JavaScript / Node.js

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)

Python

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']}")

PHP

 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

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"
  }'

Создание счёта с корзиной

JavaScript / Node.js

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

Python

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()

PHP

 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

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
  }'

Создание подписки

JavaScript / Node.js

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)

Python

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']}")

PHP

 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

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"
  }'

Создание подписки с корзиной

JavaScript / Node.js

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()

Python

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()

PHP

 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

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 }
    ]
  }'

Загрузка изображения и создание товара

JavaScript / Node.js

// 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
    }]
  })
})

Python

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
        }]
    }
)

PHP

 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);

cURL

# 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\"
    }]
  }"

Возврат средств

JavaScript / Node.js

// 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' })
})

Python

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'}
)

PHP

 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);

cURL

# 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"}'

Коды ошибок

КодОписание
400Bad Request — некорректный запрос или состояние
401Unauthorized — неверный, отсутствующий или истёкший API ключ
403Forbidden — организация не верифицирована
404Not Found — ресурс не найден
422Validation Error — ошибка валидации полей
429Too Many Requests — превышен rate limit (проверьте retry_after)
500Server Error — ошибка сервера
502Bad Gateway — ошибка Kaspi API
503Service Unavailable — Kaspi сессия истекла

Формат ответа ошибки

{
  "message": "Описание ошибки",
  "errors": {
    "field_name": ["детали ошибки"]
  }
}

Changelog