# Pagination

> Cursor pagination of Slicer API lists — the limit and cursor parameters, the nextCursor field and examples that walk through every page.

Source: https://docs.slicer.media/en/api/pagination/

Long lists — projects and wallet transactions — come in pages. Pagination is cursor-based: each page tells you where the next one starts, and you simply pass that value to the next request.

## Parameters and response

| Parameter | Value |
| --- | --- |
| `limit` | page size, 1 to 100; 20 by default |
| `cursor` | the `nextCursor` value from the previous page; omit it for the first page |

Response of GET /v1/projects?limit=2:

```json
{
  "ok": true,
  "data": {
    "items": [
      { "id": 318, "title": "A reel in 3 minutes", "status": "succeeded" },
      { "id": 311, "title": "New collection overview", "status": "succeeded" }
    ],
    "nextCursor": "Mg"
  }
}
```

`nextCursor: null` means there are no more pages. A cursor is an opaque string: don't parse it or build it yourself. A damaged cursor gets `400 bad_cursor`.

Paginated lists:

-   `GET /v1/projects` — projects, newest first; filters `status` (`all`, `ready`, `active`, `failed`, `draft` — drafts of projects without the AI director) and `q` — search by title;
-   `GET /v1/wallet/transactions` — wallet transactions, newest first.

Other lists (versions, project history, publications, webhooks, deliveries) are short and come as a plain array.

## Walk through every page

**curl**

Terminal:

```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"])
```

> **Note.** A cursor doesn't freeze a snapshot of the list. If projects are created or deleted while you walk through the pages, items at page boundaries may repeat or shift. De-duplicate by `id`, and catch new projects with the `project.succeeded` [webhook](https://docs.slicer.media/en/api/webhooks/) rather than by walking the list again.

## What's next

[ProjectsThe project list and its filters in the reference.](https://docs.slicer.media/en/api/reference/projects/)[AccountWallet transactions.](https://docs.slicer.media/en/api/reference/account/)
