Массовая заливка каталога 1С в ApiPay: очередь, ETA, ошибки

Обновлено 11 июля 2026 · Справочник · Версия в Markdown
Содержание
  1. Шаг 1. Заливка батчами по 100 с `Idempotency-Key`
  2. Шаг 2. Следим за очередью и ETA
  3. Шаг 3. Итог конкретного залива
  4. Шаг 4. Один вебхук на весь залив
  5. Шаг 5. Разбор ошибок
  6. Шаг 6. Повторная заливка идемпотентна
  7. Частые ошибки
  8. Частые вопросы
  9. Что дальше

Шаг 1. Заливка батчами по 100 с `Idempotency-Key`

Разбейте каталог на батчи до 100 позиций и отправляйте их по очереди. На каждый батч ставьте свой Idempotency-Key — стабильную строку (например upload-2026-07-11-part-042). Если сеть оборвалась и вы не знаете, дошёл ли запрос, повторите его с тем же ключом — ApiPay вернёт уже принятый батч, а не создаст позиции второй раз.

curl -X POST https://api.apipay.kz/api/v1/catalog \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: upload-2026-07-11-part-042" \
  -d '{
    "items": [
      { "name": "Ручка гелевая синяя 0.5", "selling_price": 350, "unit_id": 1,
        "barcode": "4870000000001", "external_ref": "1c-000123" },
      { "name": "Тетрадь 48 листов клетка", "selling_price": 420, "unit_id": 1,
        "barcode": "4870000000002", "external_ref": "1c-000124" }
    ]
  }'

Ответ — 202 (принято в обработку), в нём приходит блок batch с batch_id:

{
  "data": [
    { "id": 5001, "external_ref": "1c-000123", "status": "pending",
      "matched_existing": false, "name_differs": false }
  ],
  "rejected": [],
  "batch": {
    "batch_id": "8f14e45f-ceea-467e-9a2c-0000000000aa",
    "status": "accepted",
    "totals": { "total": 2, "created": 0, "updated": 0, "skipped": 0, "failed": 0 },
    "pending_remaining": 2,
    "poll_url": "https://api.apipay.kz/api/v1/catalog/batches/8f14e45f-ceea-467e-9a2c-0000000000aa",
    "created_at": "2026-07-11T10:00:00+00:00",
    "completed_at": null
  }
}

Сохраните batch_id — он ключ к прогрессу и к разбору ошибок этого залива. data содержит принятые позиции (новые — со status: pending), rejected — позиции, не прошедшие структурную валидацию (например без name), их можно исправить и переотправить.

Ставьте external_ref (вашу ссылку 1С) у каждой позиции — это ключ маппинга и якорь идемпотентности. Почему не штрихкод — см. Каталог для 1С.

Шаг 2. Следим за очередью и ETA

Большой каталог обрабатывается ровным потоком — примерно 200 позиций в минуту. Чтобы показать клиенту прогресс, читайте GET /catalog/queue:

curl -G https://api.apipay.kz/api/v1/catalog/queue \
  -H "X-API-Key: YOUR_API_KEY"
{
  "current_page": 1,
  "data": [
    { "id": 5001, "external_ref": "1c-000123", "name": "Ручка гелевая синяя 0.5",
      "queued_at": "2026-07-11T10:00:00+05:00" }
  ],
  "total": 1234,
  "queue": {
    "state": "draining",
    "ahead_in_cashier_queue": 2200,
    "eta_minutes": 11,
    "throttle_retry_in_seconds": null
  }
}
  • total — сколько ваших позиций ещё ждёт обработки.
  • queue.eta_minutes — оценка времени до конца очереди в минутах. Приходит целым числом только когда очередь реально двигается (state: draining); в остальных состояниях — null (не показывайте «0 минут»).
  • queue.ahead_in_cashier_queue — сколько позиций стоит впереди с учётом всех организаций одного кассира. Если у кассира несколько организаций, они делят одну очередь — ETA это уже учитывает.
  • queue.state — почему очередь в текущем состоянии:
state Что значит Что делать
draining очередь двигается показывать eta_minutes
paused_throttle пауза из-за ограничения частоты обработки подождать throttle_retry_in_seconds секунд, обработка возобновится сама
paused_hold накопление приостановлено владельцем (hold-режим) снять hold в кабинете, чтобы очередь пошла
direct приём в очередь выключен — позиции обрабатываются напрямую это не ошибка; ETA неприменим
not_connected у организации не подключён кассир Kaspi подключить/переавторизовать кассира
sandbox режим песочницы позиции активируются мгновенно, без очереди

Поллите очередь раз в 5–15 секунд — лимит 600 запросов/мин на ключ это позволяет. Останавливайте поллинг, когда total дошёл до нуля.

Шаг 3. Итог конкретного залива

Прогресс именно вашего батча — GET /catalog/batches/{id} (используйте poll_url из ответа POST):

curl https://api.apipay.kz/api/v1/catalog/batches/8f14e45f-ceea-467e-9a2c-0000000000aa \
  -H "X-API-Key: YOUR_API_KEY"
{
  "batch_id": "8f14e45f-ceea-467e-9a2c-0000000000aa",
  "status": "partially_failed",
  "totals": { "total": 6870, "created": 5868, "updated": 3, "skipped": 996, "failed": 3 },
  "pending_remaining": 0,
  "poll_url": "https://api.apipay.kz/api/v1/catalog/batches/8f14e45f-ceea-467e-9a2c-0000000000aa",
  "created_at": "2026-07-11T10:00:00+00:00",
  "completed_at": "2026-07-11T10:12:00+00:00"
}
  • status: acceptedprocessingcompleted (всё ОК) либо partially_failed (есть упавшие).
  • pending_remaining — сколько позиций залива ещё не финализировано (0 = готово).
  • totals по завершении: total = created + updated + skipped + failed. skipped — это позиции, которые уже были в каталоге без изменений (идемпотентный пропуск) или совпали внутри батча; в Kaspi по ним обращений нет.

Шаг 4. Один вебхук на весь залив

Если у вас есть публичный URL для вебхуков, не ждите тысячи per-item уведомлений: когда все позиции залива финализированы, ApiPay шлёт один вебхук catalog.batch_processed (HMAC-подпись, как у остальных событий):

{
  "event": "catalog.batch_processed",
  "batch_id": "8f14e45f-ceea-467e-9a2c-0000000000aa",
  "status": "partially_failed",
  "totals": { "total": 6870, "created": 5868, "updated": 3, "skipped": 996, "failed": 3 },
  "sample_failed": [ { "external_ref": "1C-000456", "error_code": "catalog_item_duplicate" } ],
  "poll_url": "https://api.apipay.kz/api/v1/catalog/batches/8f14e45f-ceea-467e-9a2c-0000000000aa",
  "timestamp": "2026-07-11T10:12:00+00:00"
}
  • sample_failed — до 50 упавших позиций (external_ref + error_code), для быстрой реакции. Полный список ошибок — через GET /catalog/errors?batch_id=....
  • Вебхук может прийти повторно (доставка «хотя бы один раз») — дедуплицируйте по паре (batch_id, status).
  • Пока идёт батч-заливка, отдельные catalog.item_processed по её позициям не приходят — их заменяет этот агрегат. Финал по каждой позиции берите из GET /catalog/batches/{id} или из журнала ошибок.

Шаг 5. Разбор ошибок

Упавшие позиции залива читайте через GET /catalog/errors, фильтруя по batch_id:

curl -G https://api.apipay.kz/api/v1/catalog/errors \
  -H "X-API-Key: YOUR_API_KEY" \
  --data-urlencode "batch_id=8f14e45f-ceea-467e-9a2c-0000000000aa"
{
  "current_page": 1,
  "data": [
    { "id": 456, "external_ref": "1C-000456", "name": "Фильтр воздушный",
      "barcode": "4600000000001", "ntin": "00000000000001",
      "error_code": "catalog_item_duplicate",
      "error_message": "Такая позиция уже есть в каталоге.",
      "queued_at": "2026-07-11T10:00:00+05:00",
      "failed_at": "2026-07-11T10:03:00+05:00",
      "batch_id": "8f14e45f-ceea-467e-9a2c-0000000000aa" }
  ],
  "total": 3
}

Без batch_id эндпоинт отдаёт ошибки за последние 7 дней; явные from/to (по времени постановки в очередь) снимают это ограничение. Причину всегда определяйте по error_code, а не по тексту error_message (текст обезличен). Что делать с типовыми кодами:

error_code Что значит Что делать
catalog_item_duplicate Kaspi считает позицию дублем уже заведённого товара (похожее наименование) Найдите товар через GET /catalog и правьте существующий PATCH /catalog/{id}; проверьте, нет ли в вашем каталоге двух позиций с одинаковым именем
barcode_too_long штрихкод длиннее 32 символов (лимит Kaspi) Обрежьте/исправьте штрихкод и переотправьте позицию
catalog_item_invalid Kaspi отклонил данные позиции (наименование/цена/поля) Проверьте наименование, цену и штрихкод; исправьте и переотправьте

Исправив вход, переотправьте только упавшие позиции — по их external_ref.

Шаг 6. Повторная заливка идемпотентна

Гоняйте синк по расписанию без страха дублей:

  • Повтор запроса с тем же Idempotency-Key возвращает уже принятый батч — позиции не создаются заново.
  • Повтор каталога: неизменённые позиции ApiPay пропускает по внутреннему фингерпринту (они попадают в skipped), обращений в Kaspi по ним нет. Изменённые — обновляются, новые — создаются.
  • Ключ сопоставленияexternal_ref. Пока он стабилен, повторная заливка находит те же товары и не плодит «мёртвых» строк.

Так заливка 25 000 позиций в первый раз занимает ~2 часа, а последующие синки проходят почти мгновенно — работает только дельта.

Частые ошибки

  • Батч больше 100 позиций. POST /catalog принимает до 100 items за запрос. Бейте каталог на батчи по 100 (или меньше) и лейте их подряд.
  • Заливка в много потоков ради скорости. Частые параллельные запросы на один кассир не ускоряют, а вызывают паузы обработки (state: paused_throttle). Лейте батчами по 100 последовательно — ровный поток надёжнее.
  • Маппинг по штрихкоду вместо external_ref. По одному штрихкоду у мерчанта бывает несколько SKU — сверка по штрихкоду путает id. Ключ — только external_ref.
  • Ожидание active в ответе POST. 202 — это «принято», а не «готово». Финал берите из GET /catalog/batches/{id}, вебхука или поллинга.
  • Показ «0 минут» при eta_minutes: null. null — легитимно (очередь не в состоянии draining). Показывайте прочерк, а не ноль.
  • Отсутствие дедупа батч-вебхука. catalog.batch_processed доставляется «хотя бы один раз» — дедуплицируйте по (batch_id, status).

Частые вопросы

Какого размера делать батчи?

До 100 позиций за один POST /catalog. Больше — запрос отклонится валидацией. Лейте батчи подряд, ровным потоком.

Зачем нужен Idempotency-Key, если каталог и так идемпотентен?

Match-and-merge защищает от дублей на уровне товаров, а Idempotency-Key — от повторной обработки того же запроса при обрыве сети: вы можете безопасно повторить POST, не зная, дошёл ли он.

Сколько времени займёт заливка 25 000 позиций?

Около 2 часов при темпе ≈200 позиций/мин на кассира. Точную оценку остатка показывает eta_minutes в GET /catalog/queue.

Очередь показывает state: direct — это ошибка?

Нет. Это значит, что приём в очередь для организации сейчас работает в прямом режиме: позиции обрабатываются, просто без ETA. Дождитесь финала через GET /catalog/batches/{id}.

Как узнать, какие позиции упали при большой заливке?

GET /catalog/errors?batch_id=<batch_id> вернёт упавшие позиции этого залива с error_code. Чините вход по коду и переотправляйте только упавшие.

Придёт ли per-item вебхук catalog.item_processed при батч-заливке?

Нет — на время заливки его заменяет один агрегат catalog.batch_processed. Одиночные позиции (не в батче) шлют per-item вебхук как раньше.

Что дальше

Интеграция с ИИ

Не настраивайте вручную — поручите ИИ

Скопируйте готовый промпт и вставьте своему ИИ-ассистенту — он подключит приём платежей примерно за 15 минут. Настраивает ИИ, а не вы.

Для разработчика. Полная спецификация API — apipay.kz/docs. Эта статья в Markdown — /guides/massovaya-zagruzka-kataloga-iz-1c.md. Справочник фактов для ИИ-агента — apipay.kz/llms.txt.