AgentPay

Документация · REST API

Документация API

REST API для приёма платежей через Kaspi: счета с push-уведомлением, платёжные ссылки без телефона клиента, подписанные вебхуки, песочница для разработки, обмен с 1С и CLI для AI-агентов и людей. Всё на одной странице — ищи Cmd+F.

Получить API-ключMarkdown для агентовOpenAPI 3.1
Base URL: https://agentpay.kz/apiBearer-аутентификацияВебхуки HMAC-SHA256

С чего начать

AgentPay даёт три способа принимать оплату через Kaspi: веб-интерфейс, REST API и CLI. Веб работает сразу после регистрации. Для API и CLI:

  1. Создай ключ в /app/settings/api-keys. Для разработки — sk_test_* (песочница, деньги не списываются, кассир Kaspi не нужен). Для боя — sk_live_* и подключённый кассир в /app/cashier.
  2. Сделай первый запрос:
    bash
    curl -X POST https://agentpay.kz/api/v1/invoices \
      -H "Authorization: Bearer sk_test_..." \
      -H "Idempotency-Key: $(uuidgen)" \
      -H "Content-Type: application/json" \
      -d '{
        "client_phone": "77001234567",
        "amount_kzt": 1500,
        "comment": "Бронь #123",
        "external_id": "BK-2026-000123",
        "metadata": { "room": "12A", "guests": 2 }
      }'

    Ответ — JSON с полем pay_link_url: ссылка для клиента, открывает Kaspi в один тап (отправляй её в WhatsApp, SMS, чат). Параллельно Kaspi отправит push на телефон клиента. external_id и metadata вернутся в ресурсе и в каждом вебхуке по этому счёту.

  3. Подключи webhook endpoint в /app/settings/webhooks и жди invoice.paid. В песочнице оплату можно симулировать: POST /v1/test/invoices/{id}/pay.

Аутентификация

Все запросы к https://agentpay.kz/api/v1/* требуют заголовок Authorization: Bearer <key>. Ключи делятся на:

  • sk_live_* — секретный ключ, боевой режим, реальные платежи через кассира Kaspi.
  • sk_test_* — секретный ключ, песочница: счета и ссылки не уходят в Kaspi, деньги не списываются, кассир не нужен. См. раздел «Тестовый режим».
  • pk_live_* / pk_test_* — публикуемые ключи для клиентских SDK. Сейчас не используются API-эндпоинтами (на сервере нужен только sk_*).

Ресурсы разделены по режимам: тестовый ключ видит только тестовые счета, ссылки и события, боевой — только боевые. У каждого ресурса и события есть поле mode: "live" | "test".

Ключи хранятся как HMAC-SHA256 хеши, raw-значение показывается ОДИН раз при создании. Сравнение constant-time.

Тестовый режим (sandbox)

Ключ sk_test_* включает песочницу. Всё API работает как в бою, кроме одного: Kaspi не вызывается.

  • POST /v1/invoices и POST /v1/payment_links сразу возвращают status: "success", mode: "test" и kaspi_invoice_id вида test_…. Push клиенту не уходит, кассир Kaspi не нужен.
  • Оплату, просрочку и возврат ты вызываешь сам — при этом уходят те же события и подписанные вебхуки, что и в бою:
bash
# счёт оплачен → invoice.paid + вебхук
curl -X POST https://agentpay.kz/api/v1/test/invoices/{id}/pay -H "Authorization: Bearer sk_test_..."

# счёт просрочен → invoice.expired + вебхук
curl -X POST https://agentpay.kz/api/v1/test/invoices/{id}/expire -H "Authorization: Bearer sk_test_..."

# возврат и отмена — обычными эндпоинтами
curl -X POST https://agentpay.kz/api/v1/invoices/{id}/refunds -H "Authorization: Bearer sk_test_..." -H "Idempotency-Key: $(uuidgen)"
curl -X POST https://agentpay.kz/api/v1/invoices/{id}/cancel  -H "Authorization: Bearer sk_test_..." -H "Idempotency-Key: $(uuidgen)"

{id} — id счёта или платёжной ссылки. Повторный вызов на уже оплаченном счёте вернёт 200 с ресурсом (идемпотентно); на закрытом (отменён/просрочен) — 409 invalid_state. Боевой ключ на этих эндпоинтах получает 403 test_mode_required.

  • pay_link_url / pay_page_url тестового счёта открывает hosted-страницу с плашкой «Тестовый режим» и кнопкой «Симулировать оплату» — удобно для ручной проверки WhatsApp-сценария.
  • Неоплаченные тестовые счета автоматически просрочиваются через 24 часа, как боевые.
  • В CLI: agentpay test pay <id>, agentpay test expire <id>.
  • Webhook endpoint один на аккаунт и получает события обоих режимов — проверяй поле mode в теле события.

Rate limit

Лимиты на ключ, окно скользящее, минутное:

  • 60 запросов в минуту — запись (POST: создание счетов и ссылок, cancel, refund, refresh, resend).
  • 120 запросов в минуту — чтение (GET: списки, отдельные ресурсы, события).
  • SSE (/v1/events/stream) считается отдельно: 1 одновременное соединение на ключ, максимум 100 глобально.

Каждый ответ несёт заголовки:

http
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 53
X-RateLimit-Reset: 1735689600

На превышении — 429 Too Many Requests с Retry-After в секундах.

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

POST-эндпоинты, создающие или мутирующие ресурсы, требуют заголовок Idempotency-Key. Сгенерируй UUID на стороне клиента — повторный запрос с тем же ключом и тем же телом вернёт закешированный ответ (24 часа).

bash
curl -X POST https://agentpay.kz/api/v1/invoices \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: 8e9d2b4a-1f5c-4a3d-9b8a-c7e1f0d3a5b6" \
  -H "Content-Type: application/json" \
  -d '{"client_phone":"77001234567","amount_kzt":1500}'

Если повторить с тем же ключом, но другим телом — вернётся 409 idempotency_conflict: один ключ = одна операция. Практичный приём: выводи ключ из своего booking_id (например UUID v5 от строки booking:123) — тогда ретрай из любой части системы гарантированно не создаст второй счёт.

Счета

POST/v1/invoices

Создать разовый счёт: Kaspi шлёт push на телефон клиента, плюс ты получаешь pay_link_url для отправки любым каналом. Idempotency-Key обязателен.

Тело:

json
{
  "client_phone": "77001234567",
  "amount_kzt": 1500,
  "comment": "Бронь #123",
  "external_id": "BK-2026-000123",
  "metadata": { "room": "12A", "guests": 2, "source": "site" }
}
ПолеТипОбязательноеОписание
client_phonestringда11 цифр, начинается с 7, без +
amount_kztintegerдаСумма в тенге, 1…500 000
commentstring ≤200нетНазначение. Виден плательщику в Kaspi
external_idstring ≤128нетТвой id (booking_id / order_id). Не виден плательщику, возвращается в ресурсе и вебхуках, доступен как фильтр
metadataobjectнетДо 20 ключей (≤40 символов), значения string ≤500 / number / boolean / null. Не виден плательщику, возвращается в ресурсе и вебхуках

Если у тенанта нет активного кассира (боевой режим) — 409 no_active_cashier.

GET/v1/invoices

Список счетов, новые первыми. Cursor-пагинация, фильтры:

http
GET /v1/invoices?limit=20&status=paid&kind=one_time
GET /v1/invoices?external_id=BK-2026-000123
GET /v1/invoices?status=paid&paid_after=2026-09-01T00:00:00Z
GET /v1/invoices?created_after=2026-09-01T00:00:00Z
GET /v1/invoices?cursor=eyJjcmVhdGVkX2F0Ijoi...

paid_after / created_after — ISO-8601, строго после указанного момента. Это основа инкрементальной сверки (см. «Обмен с 1С»).

GET/v1/invoices/{id}

Один счёт по UUID. 404 если не найден, принадлежит другому тенанту или другому режиму.

POST/v1/invoices/{id}/cancel

Отменить неоплаченный счёт. Тело пустое. Idempotency-Key обязателен.

POST/v1/invoices/{id}/refunds

Возврат оплаченного счёта. Возвращается полная сумма. Idempotency-Key обязателен.

Invoice resource

json
{
  "id": "8e9d2b4a-1f5c-4a3d-9b8a-c7e1f0d3a5b6",
  "object": "invoice",
  "kind": "one_time",
  "status": "success",
  "mode": "test",
  "client_phone": "77001234567",
  "amount_kzt": 1500,
  "comment": "Бронь #123",
  "external_id": "BK-2026-000123",
  "metadata": { "room": "12A", "guests": 2, "source": "site" },
  "kaspi_invoice_id": "1234567",
  "receipt_url": "https://kaspi.kz/receipt/1234567",
  "pay_link_url": "https://agentpay.kz/pay/Py1L9FpqkTvg...",
  "created_at": "2026-09-07T18:27:45.752Z",
  "ran_at":     "2026-09-07T18:27:45.771Z",
  "paid_at":    null,
  "expired_at": null,
  "cancelled_at": null,
  "refunded_at": null,
  "subscription_id": null,
  "error": null
}

Три идентификатора у каждого платежа: id (наш UUID — ключ дедупликации на твоей стороне), kaspi_invoice_id (номер операции в Kaspi, в тесте test_…) и receipt_url (чек Kaspi, появляется после оплаты).

Статусы

Happy path: pendingsuccess (отправлен в Kaspi) → paid|expired|cancelled|refunded (терминальные).

СтатусЗначение
pendingСоздан в БД, ещё не отправлен в Kaspi
successОтправлен в Kaspi, ждёт оплаты
paidОплачен (подтверждено poll'ом или вебхуком)
expiredПросрочен — Kaspi пометил или вышел TTL (24 часа)
cancelledОтменён продавцом или Kaspi
refundedВозврат после оплаты

В модели Kaspi нет статуса «плательщик отказался»: клиент либо платит, либо счёт истекает через 24 часа (expired). Отказ плательщика в приложении Kaspi приходит как cancelled.

Статусы ошибок отправки — счёт сохранён, но до Kaspi не дошёл; детали в поле error:

СтатусЗначение
auth_expiredСессия кассира истекла, нужен re-auth по SMS
bannedKaspi заблокировал кассира (анти-фрод)
rate_limitedKaspi ограничил частоту запросов
network_errorСетевая ошибка при вызове Kaspi
invalid_responseKaspi ответил в неожиданном формате
errorНеклассифицированная ошибка
skipped_staleПропущено окно отправки (> 12 часов), счёт не отправлен

События

Все изменения состояния пишутся в неизменяемую таблицу events и разлетаются по подписанным webhook endpoint'ам. У каждого события уникальный UUID id.

GET/v1/events

Список событий, новые первыми, cursor-пагинация. Фильтры type, resource_id, created_after (ISO-8601).

http
GET /v1/events?type=invoice.paid&limit=20
GET /v1/events?resource_id=8e9d2b4a-...
GET /v1/events?type=invoice.paid&created_after=2026-09-07T00:00:00Z
GET/v1/events/{id}

Одно событие плюс история доставок вебхука по нему — ответ на вопрос «дошёл ли invoice.paid по брони X до моего сервера»:

json
{
  "id": "6f1e8a2c-...", "object": "event", "type": "invoice.paid", "mode": "live",
  "resource_type": "invoice", "resource_id": "8e9d2b4a-...",
  "data": { "kaspiStatus": "Confirmed", "kaspiInvoiceId": "1234567" },
  "created_at": "2026-09-07T18:35:18.000Z",
  "deliveries": [
    {
      "id": "d1…", "object": "webhook_delivery",
      "endpoint_id": "e1…", "endpoint_url": "https://your-app.com/webhooks/agentpay",
      "event_id": "6f1e8a2c-...", "status": "success",
      "attempt_count": 1, "next_attempt_at": null, "last_response_status": 200,
      "created_at": "2026-09-07T18:35:19.000Z", "updated_at": "2026-09-07T18:35:20.000Z"
    }
  ]
}

Статусы доставки: pending (в очереди / ждёт повтора), success (получен 2xx), dead_letter (8 попыток исчерпаны или endpoint отключён).

POST/v1/events/{id}/resend

Переотправить вебхук. Создаёт новую доставку (свежий график повторов) на все активные endpoint'ы, подписанные на тип события, или на один — если передать { "endpoint_id": "…" }. Ответ 202 со списком поставленных в очередь доставок. Idempotency-Key обязателен.

bash
curl -X POST https://agentpay.kz/api/v1/events/6f1e8a2c-.../resend \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: $(uuidgen)"
GET/v1/events/stream

Server-Sent Events. Стрим открыт пока соединение живо (15s ping, 30min idle disconnect). Лимиты: 1 одновременное соединение на ключ, 100 глобально (иначе 429 too_many_connections_per_key / too_many_connections). Используется CLI-командами agentpay listen и agentpay events tail.

Типы событий

ТипКогда
invoice.createdСчёт или платёжная ссылка созданы в нашей БД
invoice.sentОтправлен в Kaspi: push ушёл клиенту (счёт) или токен выпущен (ссылка)
invoice.paidКлиент оплатил
invoice.expiredПросрочен (24 часа без оплаты)
invoice.cancelledОтменён продавцом, через API или отклонён клиентом в Kaspi
invoice.refundedВозврат успешно прошёл
invoice.refund_failedВозврат не прошёл
invoice.send_failedНе удалось отправить в Kaspi (детали в data.code)
invoice.duplicate_refundedПовторная оплата по вытесненному токену автоматически возвращена (основной платёж засчитан)
subscription.createdПодписка создана
subscription.advancednext_run_at сдвинулся (новый инвойс)
subscription.pausedПодписка на паузе
subscription.resumedПодписка возобновлена
subscription.failedОчередной счёт по подписке не выставился (ошибка Kaspi)
subscription.cancelledПодписка отменена
cashier.activatedКассир прошёл SMS-верификацию
cashier.replacedКассир заменён на нового
cashier.removedКассир удалён (rotation)
cashier.bannedKaspi заблокировал
cashier.auth_expiredСессия истекла, нужен re-auth

«Failed» в терминах интегратора — это три события: invoice.send_failed (не удалось выставить), invoice.expired (клиент не оплатил) и invoice.refund_failed (возврат не прошёл).

При перевыпуске токена платёжной ссылки (POST /refresh или кнопка на pay-странице) приходит invoice.sent с data.refreshed: true — это не новый счёт, а свежий токен за тем же ресурсом.

Webhooks

Подключи endpoint в /app/settings/webhooks. При создании ты получишь whsec_* — секрет для проверки подписи. Показывается ОДИН раз, храни в .env или secret manager.

Формат запроса

http
POST /your-webhook-handler
Content-Type: application/json
X-AgentPay-Signature: t=1735689600,v1=5257a86<...>
X-AgentPay-Event-Type: invoice.paid
X-AgentPay-Event-Id: 6f1e8a2c-9d3b-4c7e-8a1f-2b5d9c0e4a7b
User-Agent: AgentPay-Webhooks/1.0

Тело — Event resource

Для событий invoice.* поле data дополняется актуальными полями счёта, чтобы не делать GET за каждой оплатой:

json
{
  "id": "6f1e8a2c-9d3b-4c7e-8a1f-2b5d9c0e4a7b",
  "object": "event",
  "type": "invoice.paid",
  "account_id": "3c1f…",
  "resource_type": "invoice",
  "resource_id": "8e9d2b4a-1f5c-4a3d-9b8a-c7e1f0d3a5b6",
  "mode": "live",
  "data": {
    "kaspiStatus": "Confirmed",
    "kaspiInvoiceId": "1234567",
    "status": "paid",
    "kind": "one_time",
    "amount_kzt": 1500,
    "client_phone": "77001234567",
    "paid_at": "2026-09-07T18:35:18.000Z",
    "external_id": "BK-2026-000123",
    "metadata": { "room": "12A", "guests": 2 },
    "kaspi_invoice_id": "1234567",
    "receipt_url": "https://kaspi.kz/receipt/1234567"
  },
  "created_at": "2026-09-07T18:35:18.000Z"
}

id события — уникальный UUID; храни его и игнорируй повторы (доставка at-least-once). resource_id — id счёта, data.external_id — твой booking_id / order_id.

Проверка подписи

HMAC-SHA256 от строки <timestamp>.<raw_body> с твоим whsec_* в качестве ключа. Сравнивай constant-time, отвергай запросы старше 5 минут. Подписывается сырое тело — не пересериализуй JSON перед проверкой.

typescript
import crypto from 'node:crypto';

function verifyAgentPayWebhook(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(
    header.split(',').map((p) => p.trim().split('=', 2) as [string, string]),
  );
  const t = Number(parts.t);
  const sig = parts.v1;
  if (!t || !sig) return false;
  if (Math.abs(Date.now() / 1000 - t) > 300) return false; // 5min replay window

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(sig, 'hex'));
}

То же на Python:

python
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t, sig = int(parts["t"]), parts["v1"]
    if abs(time.time() - t) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

Доставка

  • Timeout: 15 секунд. Дольше — считаем неудачей.
  • Успех = HTTP 2xx. Любой не-2xx — повтор по графику ниже.
  • Повторные попытки: 1 минута → 5 минут → 30 минут → 2 часа → 12 часов → 1 день → 2 дня → dead-letter (всего 8 попыток, ~3 дня).
  • Идемпотентный приёмник обязателен — мы гарантируем at-least-once. Дедуплицируй по id события.

Потерянные события: переотправка и догон

Три инструмента, от простого к надёжному:

  1. Переотправить вручную — кнопка «Переотправить» в /app/settings/webhooks или POST /v1/events/{id}/resend (agentpay events resend <id>). Историю доставок по событию смотри в GET /v1/events/{id}.
  2. Догнать после простоя — после того как твой сервер полежал дольше 3 дней (или ты хочешь сверку по расписанию), опроси GET /v1/events?type=invoice.paid&created_after=<последняя обработанная метка> и пройди по курсору. Это тот же журнал, из которого шлются вебхуки.
  3. Сверка по счетам GET /v1/invoices?status=paid&paid_after=… возвращает полные ресурсы (сумма, external_id, kaspi_invoice_id, чек). Удобно для бухгалтерии и 1С.

Обмен с 1С

Готового модуля для 1С нет — обмен строится на REST API. Два варианта, поля одинаковые.

Вариант 1 — 1С забирает оплаты сама (pull, рекомендуем для старта)

Регламентное задание раз в 5–15 минут вызывает GET /v1/invoices?status=paid&paid_after=<метка>, создаёт документы и запоминает максимальный paid_at из ответа как новую метку. Дедупликация — по id счёта (сохраняй его в реквизит документа). Если счётов больше limit, иди по next_cursor.

bsl
// 1С:Предприятие 8.3, BSL. КлючAPI — sk_live_* из /app/settings/api-keys.
Соединение = Новый HTTPСоединение("agentpay.kz", 443, , , , 30, Новый ЗащищенноеСоединениеOpenSSL());
Заголовки = Новый Соответствие;
Заголовки.Вставить("Authorization", "Bearer " + КлючAPI);
Заголовки.Вставить("Accept", "application/json");

Путь = "/api/v1/invoices?status=paid&limit=100&paid_after=" + КодироватьСтроку(МеткаISO, СпособКодированияСтроки.КодировкаURL);
Ответ = Соединение.Получить(Новый HTTPЗапрос(Путь, Заголовки));
Если Ответ.КодСостояния <> 200 Тогда
    ВызватьИсключение "AgentPay: HTTP " + Ответ.КодСостояния + " " + Ответ.ПолучитьТелоКакСтроку();
КонецЕсли;

Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(Ответ.ПолучитьТелоКакСтроку());
Результат = ПрочитатьJSON(Чтение, Ложь); // Структура: object, data (Массив), has_more, next_cursor

Для Каждого Счет Из Результат.data Цикл
    // Счет.id, Счет.external_id, Счет.amount_kzt, Счет.paid_at, Счет.client_phone,
    // Счет.kaspi_invoice_id, Счет.receipt_url, Счет.comment, Счет.metadata
    СоздатьОплатуОтПокупателя(Счет);
КонецЦикла;

Вариант 2 — вебхук в вашу прослойку (push)

AgentPay отправляет invoice.paid / invoice.refunded на ваш HTTP-сервис (в том числе опубликованный HTTP-сервис 1С). Проверьте подпись X-AgentPay-Signature, ответьте 2xx, дедуплицируйте по id события. Всё нужное для документа уже лежит в data (см. «Тело — Event resource»). Если ваш сервер был недоступен, события догоняются вариантом 1.

Маппинг полей

Поле AgentPayКуда в 1СКомментарий
idРеквизит «ID платежа AgentPay»Ключ дедупликации, UUID
external_idДокумент-основание (Заказ / Бронь)Ваш номер, передаётся при создании счёта
amount_kztСумма документаЦелое число тенге
paid_atДата документаISO-8601, UTC
client_phoneКонтрагент (поиск по телефону)11 цифр
kaspi_invoice_idНомер операции KaspiДля сверки с выпиской Kaspi
receipt_urlКомментарий / ссылка на чекЧек Kaspi
commentНазначение платежаВиден плательщику
metadataЛюбые доп. реквизитыОбъект key→value
status = "refunded", refunded_atВозврат покупателюОтдельный документ

Сумма приходит на расчётный счёт мерчанта от Kaspi по обычному регламенту Kaspi Pay; AgentPay фиксирует факт оплаты и её реквизиты, не является платёжным агентом и не держит деньги.

CLI

@kaspi-agentpay/cli — обёртка над REST API, та же функциональность за одну команду.

bash
npm install -g @kaspi-agentpay/cli

agentpay init --base-url https://agentpay.kz/api
# вставь sk_test_… (песочница) или sk_live_… когда спросит

# создать счёт с твоими идентификаторами
agentpay invoices create --phone 77001234567 --amount 1500 --comment "Бронь #123" \
  --external-id BK-2026-000123 --metadata '{"room":"12A"}'

# список + фильтры
agentpay invoices list --status paid --paid-after 2026-09-01T00:00:00Z
agentpay invoices get <invoice_id>

# отменить / вернуть
agentpay invoices cancel <invoice_id>
agentpay invoices refund <invoice_id>

# платёжные ссылки без телефона
agentpay links create --amount 5000 --external-id ORD-42
agentpay links refresh <payment_link_id>

# песочница: симулировать оплату / просрочку (sk_test_* only)
agentpay test pay <invoice_id>
agentpay test expire <invoice_id>

# события и вебхуки
agentpay events tail                      # сырой лог (SSE)
agentpay events get <event_id>            # событие + история доставок
agentpay events resend <event_id>         # переотправить вебхук
agentpay listen --forward-to http://localhost:3000/webhooks/agentpay

CLI хранит ключ в ~/.agentpay/config.json (mode 0600). Переопределяй через env AGENTPAY_API_KEY и AGENTPAY_BASE_URL.

Для AI-агентов (Claude Code, Codex, Cursor)

AgentPay спроектирован под агентов: одна страница документации (вот эта), OpenAPI-спека, один CLI с предсказуемыми флагами, плоский набор REST-эндпоинтов с типизированными ошибками.

Скармливай агенту всю спецификацию одной командой

Жми «Скопировать всю документацию» в шапке этой страницы — markdown сразу в буфер. Или подтяни через curl:

bash
curl -s https://agentpay.kz/api/docs.md | pbcopy        # → в буфер для вставки в чат агента
curl -s https://agentpay.kz/api/openapi.json > openapi.json   # → для кодогенерации клиента

Или через флаг --system / context-инъекцию агента:

bash
claude code --system "$(curl -s https://agentpay.kz/api/docs.md)" \
  "Выстави клиенту 77001234567 счёт на 5000 ₸ за Pro-подписку, используя AgentPay CLI."

Типичные промпты, которые агент сможет выполнить после инжеста

Запрос пользователяЧто сделает агент
«Выстави Алие счёт на 12 000 за консультацию»
agentpay invoices create \
  --phone 7705... \
  --amount 12000 \
  --comment "Консультация"
«Покажи, что не оплачено за неделю»
agentpay invoices list \
  --status success \
  --limit 50
# + фильтрация по дате на клиенте
«Сделай возврат по последнему счёту Алии»
agentpay invoices list  # → найти id
agentpay invoices refund <id>
«Подключи мой бэкенд к вебхукам и проверь что invoice.paid доходит»
agentpay listen \
  --forward-to http://localhost:3000/wh
# + создать тестовый счёт
agentpay test pay <id>

Правила, которые агент должен помнить

  • Каждый POST создающий ресурс требует Idempotency-Key — генерируй uuidgen на каждой логической операции, не на каждый retry.
  • Ошибки всегда JSON { "error": { "type", "message" } } type стабильное, message — для человека.
  • Status lifecycle инвойса: pendingsuccess (отправлен) → paid|expired|cancelled|refunded (терминальные). refund возможен только на paid, cancel — на pending|success.
  • Для оплаты без телефона клиента используй POST /v1/payment_links и отправляй клиенту pay_page_url (стабильная страница), а не одноразовый pay_url.
  • Для разработки используй sk_test_* — это песочница: Kaspi не вызывается, деньги не списываются, статусы переключаются через POST /v1/test/invoices/{id}/pay|expire.
  • Передавай свой external_id при создании — по нему сверяй вебхуки и списки.

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

Все ошибки — JSON, одинаковая форма:

json
{
  "error": {
    "type": "no_active_cashier",
    "message": "No active cashier on this tenant. Add one in the web app first."
  }
}
HTTPtypeКогда
400invalid_requestzod-валидация не прошла (в message — какое поле)
400invalid_jsonтело не JSON
400idempotency_key_requiredнет Idempotency-Key на мутирующем POST
401authentication_requiredнет Authorization header
401invalid_api_keyключ не найден или отозван
402subscription_requiredподписка тенанта не активна (trial или past_due)
403forbidden_scopepk_ ключ на secret endpoint
403api_requires_proAPI и CLI доступны только на тарифе Pro
403test_mode_requiredsandbox-эндпоинт вызван боевым ключом
404not_foundресурс не существует, принадлежит другому тенанту или другому режиму (live/test)
404endpoint_not_foundresend на несуществующий/отключённый webhook endpoint
409no_active_cashierу тенанта нет активного кассира (боевой режим)
409cashier_not_readyкассир есть, но status != active
409wrong_kindcancel применим только к разовым счетам
409not_sentсчёт не был принят Kaspi — отменять нечего
409already_paidотмена счёта, который уже оплачен — делай возврат
409already_cancelledсчёт уже отменён
409not_paidвозврат на неоплаченном счёте — refund только для paid
409no_cashierу счёта/ссылки нет привязанного кассира
409invalid_stateоперация невозможна в текущем статусе (напр. refresh закрытой ссылки, test pay на отменённом)
409idempotency_conflictтот же ключ + другое тело
409no_webhook_endpointsresend, но ни один активный endpoint не подписан на этот тип события
422auth_expired / banned / rate_limited / network_error / invalid_response / errorKaspi отклонил выпуск при создании — счёт/ссылка сохранены со статусом ошибки, перечитай GET-ом
429rate_limitedпревышен лимит запросов в минуту
429too_many_connections / too_many_connections_per_keySSE: превышен лимит соединений (100 глобально / 1 на ключ)
500internal_errorнеожиданная ошибка сервера
502kaspi_errorKaspi отклонил вызов

Пагинация

Списковые эндпоинты возвращают объект:

json
{
  "object": "list",
  "data": [ ... ],
  "has_more": true,
  "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0xMiIsImlkIjoiOGU5ZDJiNGEifQ"
}

Передавай next_cursor в параметре cursor следующего запроса. limit по умолчанию 20, максимум 100. Порядок — новые первыми.

Документация API — AgentPay