Пагинация

Cursor-based пагинация — стабильная при росте данных

Все списочные эндпоинты v2 используют курсорную пагинацию. Никаких offset/page — это надёжнее на больших объёмах и при изменении данных в процессе обхода.

Где применяется

  • GET /v2/payments
  • GET /v2/payment-links
  • GET /v2/events
  • GET /me/webhooks/{id}/deliveries

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

Параметры запроса

QueryТипПо умолчаниюОписание
cursorstringКурсор следующей страницы из предыдущего ответа
limitint25Размер страницы, максимум 100

Дополнительные фильтры зависят от эндпоинта — например, status, type, since.

Формат ответа

json
Скачать
{
  "data": [
    { "id": 1024, "amount": 5000, "status": "Processed" },
    { "id": 1023, "amount": 12000, "status": "Processed" }
  ],
  "next_cursor": "eyJpZCI6MTAyMywiY3JlYXRlZF9hdCI6IjIwMjYtMDUtMTEifQ==",
  "has_more": true
}
ПолеОписание
dataМассив объектов текущей страницы
next_cursorПередавайте в следующем запросе. null, если страниц больше нет
has_moretrue если есть ещё страницы

Пагинация в цикле

curl

bash
Скачать
CURSOR=""
while :; do
  RESPONSE=$(curl -s "https://payapi.aibot.kz/v2/payments?cursor=$CURSOR&limit=100" \
    -H "X-API-Key: kp_live_xxx")

  echo "$RESPONSE" | jq -r '.data[]'

  HAS_MORE=$(echo "$RESPONSE" | jq -r '.has_more')
  [[ "$HAS_MORE" == "false" ]] && break

  CURSOR=$(echo "$RESPONSE" | jq -r '.next_cursor')
done

Python

python
Скачать
import requests

def iter_payments():
    cursor = None
    while True:
        params = {"limit": 100}
        if cursor: params["cursor"] = cursor
        resp = requests.get(
            "https://payapi.aibot.kz/v2/payments",
            headers={"X-API-Key": "kp_live_xxx"},
            params=params,
        ).json()

        yield from resp["data"]

        if not resp["has_more"]:
            return
        cursor = resp["next_cursor"]

for payment in iter_payments():
    process(payment)

Node.js

javascript
Скачать
async function* iterPayments() {
  let cursor = null;
  do {
    const url = new URL("https://payapi.aibot.kz/v2/payments");
    url.searchParams.set("limit", "100");
    if (cursor) url.searchParams.set("cursor", cursor);

    const r = await fetch(url, {
      headers: { "X-API-Key": "kp_live_xxx" },
    });
    const page = await r.json();
    for (const p of page.data) yield p;
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);
}

for await (const p of iterPayments()) {
  console.log(p);
}

PHP

php
Скачать
function iterPayments() {
    $cursor = null;
    do {
        $params = ["limit" => 100];
        if ($cursor) $params["cursor"] = $cursor;

        $url = "https://payapi.aibot.kz/v2/payments?" . http_build_query($params);
        $ch  = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_HTTPHEADER => ["X-API-Key: kp_live_xxx"],
            CURLOPT_RETURNTRANSFER => true,
        ]);
        $page = json_decode(curl_exec($ch), true);

        foreach ($page["data"] as $p) yield $p;

        $cursor = $page["has_more"] ? $page["next_cursor"] : null;
    } while ($cursor);
}

foreach (iterPayments() as $payment) {
    process($payment);
}

Сортировка

По умолчанию — обратный хронологический порядок (новейшие сверху). Курсор кодирует created_at + id, поэтому стабилен даже при вставке новых записей в процессе обхода.

Почему cursor, а не offset

При ?offset=10000&limit=100 Pay Bot должен скипнуть 10000 записей в БД — это медленно на больших таблицах. Курсор работает за O(1) — мгновенно для любого размера данных.

Бонус: при вставке новых записей offset смещается, и вы можете пропустить запись или получить её дважды. С курсором такого не бывает.

Безопасность курсора

next_cursor — это base64 от JSON: {"id": 1023, "created_at": "..."}. Не модифицируйте его вручную, используйте как opaque-строку. Курсор подписан внутренним ключом — подмена приведёт к 400 invalid_cursor.

Лимит

Значение limitПоведение
0 или меньше400 invalid_limit
1–100OK
Больше 100400 limit_too_large
Не указан25 по умолчанию

Что дальше