# Пагинация

> Курсорная пагинация списков Slicer API — параметры limit и cursor, поле nextCursor и примеры обхода всех страниц.

Источник: https://docs.slicer.media/api/pagination/

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

## Параметры и ответ

| Параметр | Значение |
| --- | --- |
| `limit` | размер страницы, от 1 до 100; по умолчанию 20 |
| `cursor` | значение `nextCursor` из предыдущей страницы; без него — первая страница |

Ответ GET /v1/projects?limit=2:

```json
{
  "ok": true,
  "data": {
    "items": [
      { "id": 318, "title": "Монтаж за 3 минуты", "status": "succeeded" },
      { "id": 311, "title": "Обзор новой коллекции", "status": "succeeded" }
    ],
    "nextCursor": "Mg"
  }
}
```

`nextCursor: null` — страниц больше нет. Курсор — непрозрачная строка: не разбирайте и не составляйте её сами. Повреждённый курсор — `400 bad_cursor`.

Списки со страницами:

-   `GET /v1/projects` — проекты, новые сверху; фильтры `status` (`all`, `ready`, `active`, `failed`, `draft` — черновики проектов без ИИ-режиссёра) и `q` — поиск по названию;
-   `GET /v1/wallet/transactions` — операции кошелька, новые сверху.

Остальные списки (версии, история проекта, публикации, вебхуки, доставки) короткие и приходят целиком массивом.

## Обойти все страницы

**curl**

Терминал:

```bash
CURSOR=""
while :; do
  PAGE=$(curl -s "https://api.slicer.media/v1/projects?limit=100&status=ready${CURSOR:+&cursor=$CURSOR}" \
    -H "Authorization: Bearer $SLICER_TOKEN")
  echo "$PAGE" | jq -r '.data.items[] | "\(.id)\t\(.title)"'
  CURSOR=$(echo "$PAGE" | jq -r '.data.nextCursor // empty')
  [ -z "$CURSOR" ] && break
done
```

**JavaScript**

projects.mjs:

```js
async function* allProjects(params = {}) {
  let cursor = null;
  do {
    const query = new URLSearchParams({ limit: '100', ...params, ...(cursor ? { cursor } : {}) });
    const response = await fetch(`https://api.slicer.media/v1/projects?${query}`, {
      headers: { Authorization: `Bearer ${process.env.SLICER_TOKEN}` },
    });
    const json = await response.json();
    if (!json.ok) throw new Error(`${json.error.code}: ${json.error.message}`);
    yield* json.data.items;
    cursor = json.data.nextCursor;
  } while (cursor);
}

for await (const project of allProjects({ status: 'ready' })) {
  console.log(project.id, project.title);
}
```

**Python**

projects.py:

```python
import os

import requests

def all_projects(**params):
    session = requests.Session()
    session.headers["Authorization"] = f"Bearer {os.environ['SLICER_TOKEN']}"
    cursor = None
    while True:
        query = {"limit": 100, **params, **({"cursor": cursor} if cursor else {})}
        body = session.get("https://api.slicer.media/v1/projects", params=query, timeout=60).json()
        if not body["ok"]:
            raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
        yield from body["data"]["items"]
        cursor = body["data"]["nextCursor"]
        if not cursor:
            break

for project in all_projects(status="ready"):
    print(project["id"], project["title"])
```

> **Заметка.** Курсор не фиксирует снимок списка. Если во время обхода проекты создаются или удаляются, записи на стыке страниц могут повториться или сдвинуться. Убирайте повторы по `id`, а новые проекты ловите [вебхуком](https://docs.slicer.media/api/webhooks/) `project.succeeded`, а не повторным обходом.

## Что дальше

[ПроектыСписок проектов и фильтры в справочнике.](https://docs.slicer.media/api/reference/projects/)[АккаунтОперации кошелька.](https://docs.slicer.media/api/reference/account/)
