offset/page — это надёжнее на больших объёмах и при изменении данных в процессе обхода.Где применяется
- •
GET /v2/payments - •
GET /v2/payment-links - •
GET /v2/events - •
GET /me/webhooks/{id}/deliveries
Все они принимают одинаковые параметры и возвращают одинаковый формат.
Параметры запроса
| Query | Тип | По умолчанию | Описание |
|---|---|---|---|
cursor | string | — | Курсор следующей страницы из предыдущего ответа |
limit | int | 25 | Размер страницы, максимум 100 |
Дополнительные фильтры зависят от эндпоинта — например, status, type, since.
Формат ответа
{
"data": [
{ "id": 1024, "amount": 5000, "status": "Processed" },
{ "id": 1023, "amount": 12000, "status": "Processed" }
],
"next_cursor": "eyJpZCI6MTAyMywiY3JlYXRlZF9hdCI6IjIwMjYtMDUtMTEifQ==",
"has_more": true
}| Поле | Описание |
|---|---|
data | Массив объектов текущей страницы |
next_cursor | Передавайте в следующем запросе. null, если страниц больше нет |
has_more | true если есть ещё страницы |
Пагинация в цикле
curl
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')
donePython
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
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
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–100 | OK |
| Больше 100 | 400 limit_too_large |
| Не указан | 25 по умолчанию |
Что дальше
- •Журнал событий — главный пример курсорной пагинации
- •API Reference — все списочные эндпоинты