Qut Pay Сайт Кабинет База знаний Инструкции Документация API ҚАЗРУС
ГлавнаяБаза знаний → Справочник

Массовое создание счетов — до 100 счетов в одном запросе

Обновлено: 2026-09-14 · Версия в Markdown

Коротко

Когда нужно выставить сразу много счетов, используйте POST /api/v1/invoices/bulk. В один запрос помещается от 1 до 100 элементов.

Главное свойство метода: каждый элемент проверяется и выполняется отдельно. Если у одного получателя неверный телефон, все остальные счета всё равно будут созданы. Поэтому ответ читают не как «прошло / не прошло», а поэлементно.

Запрос

POST /api/v1/invoices/bulk
X-API-Key: qp_live_…
Idempotency-Key: payroll-2026-09-14
Content-Type: application/json

{
  "invoices": [
    { "amount": 12000, "kind": "phone", "customer": { "phone": "77011234567" }, "externalOrderId": "drv-101" },
    { "amount": 12000, "kind": "phone", "customer": { "phone": "77017654321" }, "externalOrderId": "drv-102" },
    { "amount": 8500,  "kind": "qr",    "description": "Сентябрь",              "externalOrderId": "drv-103" }
  ]
}

Внутри каждого элемента — те же поля, что и при создании одиночного счёта: amount, kind, description, externalOrderId, customer, successUrl, failUrl, metadata. Ни новых полей, ни потерянных.

ОграничениеЗначение
Минимум элементов1
Максимум элементов100
Смешанные типыВ одном списке можно и qr, и phone
КлючОдин ключ, одна организация, один кассир

Ответ

Код ответа — 207: «у каждого элемента свой результат». Структура:

{
  "total": 3,
  "created": 2,
  "failed": 1,
  "results": [
    { "index": 0, "ok": true,  "id": "inv_7Kd2", "status": "pending", "payUrl": "https://qut.kz/p/…", "externalOrderId": "drv-101" },
    { "index": 1, "ok": true,  "id": "inv_7Kd3", "status": "pending", "payUrl": "https://qut.kz/p/…", "externalOrderId": "drv-102" },
    { "index": 2, "ok": false, "error": "invalid_amount", "message": "Неверная сумма", "externalOrderId": "drv-103" }
  ]
}
ПолеЗначение
totalСколько элементов отправлено
createdСколько создано успешно
failedСколько упало
results[].indexПорядковый номер в вашем списке, с нуля
results[].okПрошёл ли этот элемент
results[].errorКод ошибки, если упал

Порядок массива results совпадает с порядком отправки, так что по index вы сопоставляете результат со своими данными. Но надёжнее в каждом элементе указывать externalOrderId — он возвращается и в ответе, и потом в вебхуке.

Обработка ошибок

Главный принцип: повторять нужно не всю пачку, а только упавшие элементы.

const res = await fetch(`${API}/invoices/bulk`, {
  method: 'POST',
  headers: {
    'X-API-Key': KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': batchKey,
  },
  body: JSON.stringify({ invoices: batch }),
}).then((r) => r.json());

const retry = [];
for (const r of res.results) {
  if (r.ok) {
    saveInvoice(batch[r.index], r.id, r.payUrl);
    continue;
  }
  if (['invalid_amount', 'invalid_phone', 'amount_must_be_whole_tenge'].includes(r.error)) {
    // ошибка в самих данных — повтор бессмысленен, показываем оператору
    reportToOperator(batch[r.index], r.error);
  } else if (['invoice_create_failed', 'kaspi_error'].includes(r.error)) {
    // временный сбой — можно повторить позже
    retry.push(batch[r.index]);
  }
}

Разделяйте ошибки по типу:

ГруппаПримеры кодовЧто делать
Ошибка в данныхinvalid_amount, invalid_phone, invalid_kind, amount_must_be_whole_tengeНе повторять без исправления
Временный сбойinvoice_create_failed, kaspi_errorПовторить с задержкой
Лимитtariff_limit_reached, tariff_daily_burstОстановиться, разобраться с тарифом
Привязкаkaspi_session_expired, no_providerВосстановить кассира, затем повторить

Есть и случаи, когда падает весь запрос целиком: неверный ключ (unauthorized), нет права (insufficient_scope), список пуст или в нём больше 100 элементов. Тогда results не приходит вовсе, возвращается обычное { error, message }.

Идемпотентность

В массовом запросе тоже работает заголовок Idempotency-Key. При повторной отправке с тем же ключом новые счета не создаются, а возвращается прежний результат.

Это особенно важно при обрыве связи: не получив ответ, вы не гадаете, создались счета или нет, а просто отправляете запрос заново с тем же ключом и читаете результат.

Делайте ключ осмысленным и привязанным к содержимому: payroll-2026-09-14 или orders-batch-4471. А externalOrderId внутри каждого элемента защищает на уровне отдельного счёта — лучше использовать оба механизма сразу. Подробнее: Счета дублируются.

Влияние на лимиты тарифа

Пачка — не льгота по лимитам. Каждый успешно созданный счёт считается и в месячном лимите, и в суточной защите отдельно.

ТарифСчетов в месяцСуточная защита
Пробный50
Старт800200
Бизнес4 0001 500
Про15 0005 000

То есть на тарифе «Старт» больше 200 счетов в сутки не выставить — две пачки по 100 уже упираются в потолок. При достижении лимита элементы начнут падать с tariff_limit_reached или tariff_daily_burst. Это разные вещи: первое — месячный лимит вашего тарифа, второе — защита от зациклившейся интеграции.

Счета в песочнице в лимиты не входят, поэтому обкатайте пачку сначала ключом qp_test_….

Если Kaspi ограничивает частоту

Kaspi может ограничивать частоту обращений со своей стороны. Если после отправки пачки заметная часть элементов падает с invoice_create_failed или kaspi_error, это признак ограничения частоты.

Что делать:

  1. Уменьшите размер пачки. Вместо 100 отправляйте по 20-25 элементов.
  2. Ставьте паузу между пачками. Хватает 2-5 секунд.
  3. Повторяйте с растущей задержкой: 1, 2, 4, 8 секунд.
  4. Если пришёл 429 с заголовком Retry-After, дождитесь указанного времени.
  5. Если падают вообще все элементы, остановитесь и не отправляйте заново — причина почти всегда в привязке кассира или в тарифе.

Порядок поиска причины массовых отказов разобран отдельно: Счета массово падают.

Когда пачка нужна, а когда нет

СценарийПравильный инструмент
Таксопарк собирает месячный взнос с водителейПачка
Очередной платёж со слушателей курсаПачка
Ежемесячные счета оптовым клиентамПачка
Один заказ на сайтеОдиночный POST /invoices
Периодические платежи, у каждого свой графикПодписка

Если счетов десятки тысяч, ставьте пачки в очередь и отправляйте постепенно — попытка «выстрелить» всем объёмом сразу упрётся в суточную защиту.

Вопросы и ответы

Если один элемент упал, остальные отменяются? Нет. Созданное остаётся созданным. Транзакции здесь нет — пачка не работает по принципу «всё или ничего».

Вебхук приходит один на всю пачку? Нет, по каждому счёту приходят обычные события. Отдельно есть событие invoice.bulk — оно сообщает о самой пачке.

Что будет, если отправить больше 100? Запрос не примется целиком. Разбивайте список на части сами.

Можно ли в одной пачке использовать разных кассиров? Нет. Счета создаются через кассира, к которому привязан ключ. Для каждого кассира заведите свой ключ: API-ключи.

Видно ли такие счета в кабинете? Да, счета, созданные через API, отображаются в кабинете как обычно и попадают в экспорт.

Связанные статьи

Счета массово падают — что делатьЕсли отправить много счетов разом, Kaspi может ограничить частоту запросов кассира. Автоматических повторов нет. Пауза, отправка по очереди и разбор кодов ошибок.Каталог ошибок — что возвращает API и что делатьВсе основные коды ошибок Qut Pay API по группам: авторизация, привязка Kaspi, счета, возвраты, лимиты тарифа, вебхуки, подписки. Причина и решение для каждой.API-ключи — создание, хранение, ротацияЧем отличаются ключи qp_live_ и qp_test_, как создать ключ в кабинете, где его хранить и где хранить категорически нельзя, зачем отдельный ключ на каждую интеграцию, как заменить ключ без простоя и что происходит при удалении.QR-счёт или счёт по телефону — что выбратьПолное сравнение двух типов счёта: значение kind, что делает покупатель, нужен ли номер, ограничения описания и суммы, срок жизни и таблица сценариев с рекомендацией по каждому.Счета дублируютсяЕсли на один заказ выставляется несколько счетов, сначала надо остановить поток: удалите API-ключ — интеграция встанет в ту же секунду. Потом ищите причину и включайте идемпотентность.

Остались вопросы? WhatsApp +77788813333 · kazprose@gmail.com
Написать можно и из кабинета: Поддержка.

Qut Pay — независимый сервис, не аффилирован с АО «Kaspi Bank». Kaspi и Kaspi Pay — товарные знаки их правообладателя.