# Pagination

> Cursor pagination with limit and starting_after, and how to page through every object.

Source: https://www.ordstage.com/docs/pagination/

Endpoints that list objects return them a page at a time, oldest first, with a cursor to the next page.

> **Note** Every list follows this shape (contacts, leads, members, conversations, messages, webhooks and more),
> except the older bookings list, described [below](#bookings).


```json
{
  "object": "list",
  "data": [{ "id": "6f1c2e4a-…", "object": "contact" }],
  "has_more": true,
  "next_cursor": "6f1c2e4a-8b3d-4c5e-9f70-1a2b3c4d5e6f"
}
```

| Parameter | Meaning |
|---|---|
| `limit` | How many objects to return, 1 to 100 (default 20). |
| `starting_after` | The `next_cursor` of the previous page. Leave it out for the first page. |

Keep requesting with `starting_after` set to the last `next_cursor` until `has_more` is `false`. The cursor is
the id of the last object you received, so pages stay stable while new objects are added. A cursor that isn't
from this list answers `400 invalid_cursor`.

```bash [curl]
curl "https://api.ordstage.com/v1/contacts?limit=100&starting_after=6f1c2e4a-8b3d-4c5e-9f70-1a2b3c4d5e6f" \
  -H "Authorization: Bearer $ORDSTAGE_API_KEY"
```

```js [JavaScript]
async function* all(path) {
  let cursor = null;
  do {
    const url = new URL(`https://api.ordstage.com${path}`);
    url.searchParams.set("limit", "100");
    if (cursor) url.searchParams.set("starting_after", cursor);
    const response = await fetch(url, { headers: { Authorization: `Bearer ${process.env.ORDSTAGE_API_KEY}` } });
    const page = await response.json();
    yield* page.data;
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);
}
```

```python [Python]
import os
import requests

def all_objects(path):
    session = requests.Session()
    session.headers["Authorization"] = f"Bearer {os.environ['ORDSTAGE_API_KEY']}"
    params = {"limit": 100}
    while True:
        page = session.get(f"https://api.ordstage.com{path}", params=params, timeout=30).json()
        yield from page["data"]
        if not page["has_more"]:
            return
        params["starting_after"] = page["next_cursor"]
```

## Bookings

The bookings list (`GET /v1/calendars/{calendar_id}/bookings`) predates this convention and keeps its own shape:
`{"bookings": [...], "has_more": bool}`, filtered by date (`from`, `to`) or by change (`updated_since`), with
`limit` up to 1000. To sync every change, keep the newest `updated_at` you have seen and ask for
`updated_since` that time; results come oldest change first.
