*REST API › Guides*

# Pagination

> Lists return one page at a time. Ask for a page with `limit`, and while `pagination.hasMore` is `true` call again with the `nextCursor` you were given.

## The loop

**The pagination loop**
- Your system → API: GET /api/v1/orders?limit=50 limit defaults to 50 and is at most 500. Add includeTotal=true to get pagination.total.
- API → Your system: data and pagination { hasMore: true, nextCursor }
- While hasMore is true:
- Your system → API: GET /api/v1/orders?limit=50&cursor=<nextCursor> The same limit and sort: a cursor is bound to both. The cursor is opaque; do not build it.
- API → Your system: Next page
- hasMore: false: you have read every page.

1. **Request the first page** Send the list request with [`limit`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-limit): 1 to 500, 50 by default. Do not send a cursor.
2. **Read the page** Take the records from `data`. `pagination.hasMore` tells you whether more results exist.
3. **Ask for the next one** While `hasMore` is `true`, call again with [`cursor`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-cursor) set to `pagination.nextCursor`, and the same [`limit`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-limit) and filters.
4. **Stop** On the last page `hasMore` is `false` and `nextCursor` is `null`.

*Read every order, a page at a time*

**cURL**

```bash
# Needs curl and jq. Prints one order per line.
base="${FILMO_BASE_URL:-https://app.filmo.io}"
cursor=""
while :; do
  # Keep limit and sort identical on every page.
  set -- --data-urlencode "limit=100" --data-urlencode "sort=createdAt"
  [ -n "$cursor" ] && set -- "$@" --data-urlencode "cursor=$cursor"

  page="$(curl -sS --fail-with-body -G "$base/api/v1/orders" \
    -H "Authorization: Bearer $FILMO_API_KEY" "$@")" || { echo "$page" >&2; exit 1; }

  echo "$page" | jq -c '.data[]'
  cursor="$(echo "$page" | jq -r 'if .pagination.hasMore then .pagination.nextCursor else "" end')"
  [ -n "$cursor" ] || break
done
```

**Node**

```javascript
const base = process.env.FILMO_BASE_URL ?? "https://app.filmo.io";
const headers = { Authorization: `Bearer ${process.env.FILMO_API_KEY}` };

const orders = [];
let cursor = null;
do {
  const url = new URL("/api/v1/orders", base);
  url.searchParams.set("limit", "100"); // keep limit and sort identical on every page
  url.searchParams.set("sort", "createdAt");
  if (cursor) url.searchParams.set("cursor", cursor);

  const res = await fetch(url, { headers });
  if (!res.ok) throw new Error(res.status + " " + (await res.text()));
  const page = await res.json();

  orders.push(...page.data);
  cursor = page.pagination.hasMore ? page.pagination.nextCursor : null;
} while (cursor);

console.log(orders.length + " orders");
```

**Python**

```python
import os
import requests

base = os.environ.get("FILMO_BASE_URL", "https://app.filmo.io")
headers = {"Authorization": f"Bearer {os.environ['FILMO_API_KEY']}"}

orders, cursor = [], None
while True:
    # Keep limit and sort identical on every page.
    params = {"limit": 100, "sort": "createdAt"}
    if cursor:
        params["cursor"] = cursor
    res = requests.get(base + "/api/v1/orders", headers=headers, params=params, timeout=30)
    res.raise_for_status()
    page = res.json()

    orders += page["data"]
    if not page["pagination"]["hasMore"]:
        break
    cursor = page["pagination"]["nextCursor"]

print(len(orders), "orders")
```

## The pagination object

| Property | Type | Meaning |
| --- | --- | --- |
| `nextCursor` | string or null | Opaque cursor of the next page. `null` when there are no more results. |
| `hasMore` | boolean | Whether more results exist beyond this page. |
| `total` | integer | The number of results that match the filters across all pages. Present only when the request sets `includeTotal` to `true`. |

## Rules

- Treat the cursor as opaque. Do not build it, parse it or store it for long; it only continues the pass it came from. A cursor is at most 200 characters long, and one that was altered, or that does not match the `limit` it is sent with, is rejected.
- Some lists are not paginated: they return everything in one response and take no `limit`, `cursor` or `sort`. They are [`GET /api/v1/tags`](https://docs.filmo.io/rest-api/endpoint-reference/tags/list-tags), [`GET /api/v1/task-types`](https://docs.filmo.io/rest-api/endpoint-reference/task-types/list-task-types), [`GET /api/v1/crm/pipelines`](https://docs.filmo.io/rest-api/endpoint-reference/crm/list-pipelines), [`GET /api/v1/custom-fields`](https://docs.filmo.io/rest-api/endpoint-reference/custom-fields/list-custom-field-definitions), [`GET /api/v1/project-types`](https://docs.filmo.io/rest-api/endpoint-reference/projects/list-project-types), [`GET /api/v1/contacts/{contactId}/related-contacts`](https://docs.filmo.io/rest-api/endpoint-reference/contacts/list-the-contacts-related-to-a-contact), [`GET /api/v1/planner/services`](https://docs.filmo.io/rest-api/endpoint-reference/planner/list-the-services-to-cover) and [`GET /api/v1/planner/resources`](https://docs.filmo.io/rest-api/endpoint-reference/planner/list-the-assignable-resources). The other lists take `limit` and `cursor`.
- A cursor is bound to the [`limit`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-limit) and the [`sort`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-sort) it was issued with. Keep both identical on every page of a pass. See [Filtering and sorting](https://docs.filmo.io/rest-api/guides/filtering-and-sorting).
- Every list has a deterministic, stable order: newest first unless the operation says otherwise, with `id` as the tie-breaker. The same request gives the same order, but the cursor is a position: records created or deleted while you page can make one repeat or be skipped, so deduplicate by `id`.
- Ask for `total` only when you need it, with [`includeTotal`](https://docs.filmo.io/rest-api/endpoint-reference/orders/list-orders#param-includeTotal). It is omitted otherwise.
- A larger page means fewer requests against the limit of 120 per minute. At the maximum of 500, reading 10,000 records takes 20 requests. See [Rate limits](https://docs.filmo.io/rest-api/guides/rate-limits).

## When records change while you page

> **Following the cursor can skip records:** The cursor marks a position in the result. If a record you have already read is updated or deleted while you are still paging, the records after it shift, and one at the boundary of a page can be missed; it is not returned again in that pass. A pass that tolerates this, such as a one-off export, can follow the cursor. The cursor is a position, not a snapshot: if you sort by `updatedAt` and a record is updated while you page, it moves to the end of an ascending pass (and the following records may be skipped) or to the front of a descending one (and you may read a record twice). To keep another system in step with Filmo, do not rely on the cursor: read time windows with the `updated*` filters instead. [Syncing data](https://docs.filmo.io/rest-api/guides/syncing-data) explains how.

## Where to go next

- [Filtering and sorting](https://docs.filmo.io/rest-api/guides/filtering-and-sorting) narrows what a pass has to read.
- [Errors](https://docs.filmo.io/rest-api/guides/errors) covers what to do when a page request fails.

---

Canonical URL: https://docs.filmo.io/rest-api/guides/pagination  
Last updated: 2026-10-09