# Счета массово падают — что делать

> Если отправить много счетов разом, Kaspi может ограничить частоту запросов кассира. Автоматических повторов нет. Пауза, отправка по очереди и разбор кодов ошибок.

## Коротко

Если счета падают не по одному, а **пачками**, первым делом остановите отправку. Причина чаще всего в частоте: через одного кассира за короткое время ушло слишком много запросов. Kaspi может ограничить частоту запросов кассира, а мы **не переотправляем упавший счёт сами** — повтор это задача вашего кода. Значит, нужно остановить цикл, посмотреть на код ошибки и переотправить по очереди.

Опаснее всего код, который повторяет запрос сразу после каждой ошибки. Он только ухудшает положение и упирается в суточную защиту.

## Сначала остановитесь

Порядок действий при массовом падении:

1. **Остановите процесс отправки.** Cron, очередь, скрипт — что бы это ни было.
2. **Соберите поле `error` у последних 50 ошибок.** Одинаковые они или разные — от этого зависит решение.
3. **Отправьте вручную один счёт.** Прошёл — дело в частоте. Упал — причина в другом.
4. **Выставьте счёт вручную из кабинета.** Получилось — привязка Kaspi жива.

В экстренном случае, например если точно известно, что код ушёл в цикл, самый надёжный стоп-кран — удалить этот API-ключ в [кабинете](https://qut.kz/app). После удаления ключа не создаётся ни один счёт. Новый ключ сделаете позже.

## Разбор по кодам ошибок

| `error` | Что означает | Что делать |
|---|---|---|
| `rate_limited`, `request_rate_limited` | Превышена частота запросов | Прочитайте заголовок `Retry-After` и выждите указанное время |
| `too_many_attempts` | Слишком частые повторы одного действия | Подождите минуту |
| `tariff_daily_burst` | Сработала суточная защита | Это не бизнес-лимит. Проверьте, нет ли цикла в коде |
| `tariff_limit_reached` | Исчерпан месячный лимит счетов по тарифу | Повысьте тариф или дождитесь нового месяца |
| `invoice_create_failed` | Kaspi не принял счёт | Можно повторить с задержкой |
| `kaspi_session_expired` | Привязка кассира оборвалась | Переподключите кассира |
| `insufficient_scope`, `tariff_inactive` | Права или тариф | [API отвечает 403](/kb/ru/api-403) |
| `unauthorized` | Ключ не распознан | [API отвечает 401](/kb/ru/api-401) |

**Если код у всех один и тот же** — причина одна, и чинится она легко. **Если коды смешанные** — чаще всего дело в частоте: система отбивает запросы в разных местах.

Не путайте `tariff_daily_burst` и `tariff_limit_reached`. Первое — суточная защита, обычно следствие цикла в коде. Второе — месячный лимит вашего тарифа, то есть настоящий бизнес-показатель.

## Автоматических повторов нет

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

Повторы должны быть на вашей стороне и по таким правилам:

- **Задержка между повторами растёт.** 1, 2, 4, 8, 16 секунд. Мгновенный повтор не помогает никогда.
- **Число повторов ограничено.** Три-пять попыток, дальше — в очередь и уведомление.
- **Ошибки 4xx не повторяются.** Неверную сумму не примут ни с первого раза, ни со сотого.
- **Отправляйте `Idempotency-Key`.** Тогда при повторе новый счёт не создастся, вернётся прежний.

```js
const res = await fetch('https://api.qut.kz/api/v1/invoices', {
  method: 'POST',
  headers: {
    'X-API-Key': key,
    'Content-Type': 'application/json',
    'Idempotency-Key': `order-${orderId}`,
  },
  body: JSON.stringify({ amount, description, externalOrderId: String(orderId) }),
});
```

В качестве `Idempotency-Key` берите собственный номер заказа — тогда повтор безопасен и счёт не раздваивается.

## Отправляйте по очереди

Отправлять сто счетов за секунду незачем. Кассир — всего лишь одна роль в Kaspi, и частота его запросов тоже ограничена.

Схема, которая работает на практике:

```js
async function sendQueue(orders) {
  const failed = [];
  for (const order of orders) {
    try {
      await createInvoice(order);
    } catch (e) {
      failed.push({ order, error: e.code });
      if (e.code === 'rate_limited' || e.code === 'tariff_daily_burst') {
        await sleep(60_000); // минута паузы, потом продолжаем
      }
    }
    await sleep(300); // небольшая пауза между счетами
  }
  return failed;
}
```

Три главных принципа:

- **Отправляйте последовательно, а не параллельно.** Десять параллельных потоков — самая частая причина массового падения.
- **Оставляйте паузу между счетами.** Нескольких сотен миллисекунд достаточно.
- **Остановитесь на первой ошибке и прочитайте причину.** Слепое продолжение означает потерю всего списка.

Если в одном запросе действительно нужно несколько счетов, вместо отдельных вызовов используйте `POST /api/v1/invoices/bulk`: от 1 до 100 счетов за запрос, каждый проверяется отдельно, и в ответе видно, какие прошли, а какие упали.

## Режим накопления

В системах с постоянным большим потоком самая надёжная схема — **накапливать и отправлять постепенно**:

1. Каждое событие, требующее счёта, кладите не сразу в API, а в свою очередь
2. Один рабочий процесс забирает из очереди и отправляет по очереди
3. Упавшее остаётся в очереди и переотправляется с задержкой
4. Если и после трёх попыток не прошло, уходит уведомление человеку

Такая схема сглаживает пики: даже если покупатели сделают сто заказов разом, в API пойдёт ровный поток. И главное — если система упадёт, заказы не потеряются, а останутся в очереди.

## Чтобы не повторялось

- **Поставьте защиту от цикла.** В коде проверяйте, что по одному заказу создаётся ровно один счёт.
- **Передавайте `externalOrderId`.** Тогда видно, какому заказу принадлежит счёт, в том числе в вебхуке, и дубли замечаются сразу.
- **Ведите журнал.** Время запроса, код ответа, значение `error` — без этого причину не найти.
- **Следите за суточным счётчиком.** Приближение к суточной защите — повод проверить, нет ли цикла.

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

**Создадутся ли упавшие счета позже сами?** Нет. Переотправка — ваша задача.

**Упёрся в суточную защиту, как её снять?** Она отпускает сама со временем. Но сначала найдите причину: чаще всего в коде есть цикл.

**Сколько счетов можно отправлять одновременно?** Точного числа нет, ограничение зависит от ситуации. На практике отправка по одному с небольшой паузой не вызывает проблем никогда.

**Если подключить несколько кассиров, станет быстрее?** Нагрузка распределится, но это не основное решение. Сначала приведите в порядок логику отправки.

**Как понять, что дело не во мне, а в Kaspi?** Есть двухминутная диагностика: [Проблема у вас или у Kaspi](/kb/ru/is-it-us-or-kaspi).
