# Pagination

> Every list returns one page at a time with the newest first. Each page carries a cursor for the next one. Read one page, loop through them all, or let the SDKs do it.

Every list in the API answers one page at a time, newest first. Ask for up to 100 items with `limit`, and pass the page’s `next_cursor` as `cursor` to get the next one. These lists work this way:

| List                               | Ordered by                        |
| ---------------------------------- | --------------------------------- |
| `GET /v1/messages`                 | When each text was made or copied |
| `GET /v1/chats`                    | When BlueBot first saw each chat  |
| `GET /v1/stops`                    | When each person was stopped      |
| `GET /v1/events`                   | When each event happened          |
| `GET /v1/webhooks`                 | When each webhook was made        |
| `GET /v1/webhooks/{id}/deliveries` | When each delivery was made       |

## One page

```bash
curl "https://usebluebot.com/api/v1/messages?direction=in&limit=100" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY"
```

```ts
import BlueBot from '@usebluebot/sdk';

const client = new BlueBot({ apiKey: process.env.BLUEBOT_API_KEY });

const page = await client.messages.list({
  direction: 'in',
  limit: 100,
});
console.log(page.data);
```

```python
import os
from bluebot import BlueBot

client = BlueBot(api_key=os.environ["BLUEBOT_API_KEY"])

page = client.messages.list(direction="in", limit=100)
print(page.data)
```

The answer, `200`:

```json
{
  "object": "list",
  "data": [
    {
      "id": "msg_3Lp3VsC7hGqE5tZyKbWn2d",
      "object": "message",
      "direction": "in",
      "state": "received",
      "from": "+15555550123",
      "text": "Perfect, see you there at 3"
    }
  ],
  "has_more": true,
  "next_cursor": "WyIyMDI2LTExLTEzVDE5OjQ0OjEwLjAwMFoiLCJiMmI4ZjE0Yi0zZTM4LTRmNTQtODZiYi0wOWE2OGYwYzE2ZjEiXQ"
}
```

| Field         | What it tells you                                                 |
| ------------- | ----------------------------------------------------------------- |
| `data`        | This page’s items, newest first.                                  |
| `has_more`    | `true` when another page follows.                                 |
| `next_cursor` | Send this as `cursor` for the next page. `null` on the last page. |

| Parameter | What it does                                                                           |
| --------- | -------------------------------------------------------------------------------------- |
| `limit`   | How many items, 1 to 100. Default 25.                                                  |
| `cursor`  | Where to start: the `next_cursor` of the page before. Leave it out for the first page. |

## The next page

Send the same request with `cursor` added:

```bash
curl "https://usebluebot.com/api/v1/messages?direction=in&limit=100&cursor=WyIyMDI2LTExLTEzVDE5OjQ0OjEwLjAwMFoiLCJiMmI4ZjE0Yi0zZTM4LTRmNTQtODZiYi0wOWE2OGYwYzE2ZjEiXQ" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY"
```

```ts
import BlueBot from '@usebluebot/sdk';

const client = new BlueBot({ apiKey: process.env.BLUEBOT_API_KEY });

const page = await client.messages.list({
  direction: 'in',
  limit: 100,
  cursor: 'WyIyMDI2LTExLTEzVDE5OjQ0OjEwLjAwMFoiLCJiMmI4ZjE0Yi0zZTM4LTRmNTQtODZiYi0wOWE2OGYwYzE2ZjEiXQ',
});
console.log(page.data);
```

```python
import os
from bluebot import BlueBot

client = BlueBot(api_key=os.environ["BLUEBOT_API_KEY"])

page = client.messages.list(
    direction="in",
    limit=100,
    cursor="WyIyMDI2LTExLTEzVDE5OjQ0OjEwLjAwMFoiLCJiMmI4ZjE0Yi0zZTM4LTRmNTQtODZiYi0wOWE2OGYwYzE2ZjEiXQ",
)
print(page.data)
```

Keep every other parameter the same from page to page. A cursor says where the last page ended, not what you were looking for, so changing `direction` or `person` partway gives you a page of a different list.

A cursor is a position, not a snapshot. Lists page on each item’s time and id, so a text that arrives while you page never shifts an item onto a second page or makes you miss one. New items appear at the front of the list, which you’ve already read; to catch them, start again from the first page, or use `since` on lists that take it.

Treat a cursor as an opaque string: store it and send it back as it is. A cursor BlueBot didn’t make, or one cut short, is `400 invalid_request`.

## Every page

Loop until `has_more` is `false`:

```ts title="all-replies.ts"
import BlueBot from '@usebluebot/sdk';

const client = new BlueBot({ apiKey: process.env.BLUEBOT_API_KEY });

let cursor: string | undefined;
do {
    const page = await client.messages.list({ direction: 'in', limit: 100, cursor });
    for (const message of page.data) {
        console.log(message.receivedAt, message.from, message.text);
    }
    cursor = page.hasMore ? (page.nextCursor ?? undefined) : undefined;
} while (cursor);
```

```python title="all_replies.py"
import os
from bluebot import BlueBot

client = BlueBot(api_key=os.environ["BLUEBOT_API_KEY"])

cursor = None
while True:
    page = client.messages.list(direction="in", limit=100, cursor=cursor)
    for message in page.data:
        print(message.received_at, message.from_, message.text)
    if not page.has_more:
        break
    cursor = page.next_cursor
```

### Let the SDK page for you

A list call in the SDKs returns its first page, and iterating over the result walks every page after it, asking for the next one only when you get there:

```ts title="all-replies.ts"
for await (const message of client.messages.list({ direction: 'in', limit: 100 })) {
    console.log(message.receivedAt, message.from, message.text);
}
```

```python title="all_replies.py"
for message in client.messages.list(direction="in", limit=100):
    print(message.received_at, message.from_, message.text)
```

With the async Python client, use `async for message in client.messages.list(...)`. Break out of the loop when you have what you need; no more pages are fetched after that.

## Big reads

- **Ask for 100 at a time.** `limit=100` reads in one request what the default of 25 takes four to read. Every page is a request against your 120 a minute. [Rate limits](https://usebluebot.com/docs/concepts/rate-limits)
- **Narrow the list first.** `GET /v1/messages` takes `since`, `until`, `person`, `contact_id`, `chat_id`, `state`, `direction` and `source`. A narrow list has fewer pages.
- **Keep your place.** For a sync that runs every few minutes, keep the newest `created_at` you’ve stored and ask with `since` next time, rather than paging everything again. `since` includes that moment, so skip any `id` you already have.
- **Catch up on missed webhooks** with `GET /v1/events?since=…`, which keeps 30 days of events. [Webhooks](https://usebluebot.com/docs/webhooks)
