# Pagination

> How TwittrAPI paginates with X's cursors, where to find the bottom cursor in the raw timeline JSON, and when to stop.

HTML page: https://www.twittrapi.com/docs/helpful-infos/cursor

Timelines, followers, search and similar endpoints return one page per call. To get the next page, send the **bottom cursor** of the current page as the `cursor` query parameter. Each page is one billed call.

Endpoints that paginate say so on their reference page. They take `count` (default 20) and `cursor`.

## Where the cursor is

The API passes X's JSON through, so the cursor sits where X puts it: in the timeline's `instructions`, as an entry whose `entryId` starts with `cursor-bottom-`:

```json
{
  "type": "TimelineAddEntries",
  "entries": [
    { "entryId": "tweet-1790000000000000000", "content": { "...": "..." } },
    {
      "entryId": "cursor-bottom-1790000000000000000",
      "content": {
        "entryType": "TimelineTimelineCursor",
        "cursorType": "Bottom",
        "value": "DAABCgABGc..."
      }
    }
  ]
}
```

Where the `instructions` array is depends on the endpoint. For example:

- User tweets: `data.user.result.timeline.timeline.instructions`
- Tweet replies: `data.threaded_conversation_with_injections_v2.instructions`
- Search: the API already unwraps it. `/search` returns `{"category": ..., "entries": [...]}` and `entries` is the instructions array.

On later pages X often moves the cursor into a `TimelineReplaceEntry` instruction (`"entry": {...}`) instead of the entries list. There is also a `cursor-top-` entry; ignore it unless you want to poll for newer items.

Because the paths differ and X changes them, the simplest robust approach is to search the whole response for an object with `"cursorType": "Bottom"` and take its `value`.

## Find the cursor yourself

```python
def bottom_cursor(node):
    """Returns the bottom cursor anywhere in X's JSON, or None."""
    if isinstance(node, dict):
        if node.get("cursorType") == "Bottom" and isinstance(node.get("value"), str):
            return node["value"]
        for value in node.values():
            found = bottom_cursor(value)
            if found:
                return found
    elif isinstance(node, list):
        for item in node:
            found = bottom_cursor(item)
            if found:
                return found
    return None
```

```bash
curl 'https://api.twittrapi.com/user/12/followers?count=20&cursor=DAABCgABGc...' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

## Let the SDK do it

The SDKs have a paginator that finds the cursor and keeps calling:

```python
from twittrapi import TwittrAPI

client = TwittrAPI()
for page in client.paginate(client.users.followers, "12", count=100, max_pages=5):
    ...  # page is the raw JSON of one call
```

```js
for await (const page of client.paginate(client.users.followers, { userId: "12", count: 100 }, { maxPages: 5 })) {
  // page.data is the raw JSON of one call, page.nextCursor the cursor for the next
}
```

`extract_bottom_cursor` (Python) and `extractBottomCursor` (Node.js) give you just the cursor if you page by hand.

## When to stop

Stop when any of these is true:

- No bottom cursor in the response.
- The cursor is the same as the one you sent.
- The page has no entries other than cursors. X keeps returning a cursor at the end of a timeline, so this check matters.
- You have as many items or pages as you need. Always set a limit; every page costs a call.

## Notes

- `count` is passed to X as is. X decides the real page size and often returns a few more or fewer items.
- Cursors are opaque strings from X. Store them as they are; they can stop working after a while.
- `/tweet/{tweetId}` uses `cursor` to load more replies of the conversation. Its page size is fixed by X.
- Coming from `has_next_page`, `next_cursor` or `meta.next_token`? The migration guides show the differences: [from twitterapi.io](https://www.twittrapi.com/guides/migrate-from-twitterapi-io), [from SocialData](https://www.twittrapi.com/guides/migrate-from-socialdata), [from the official X API](https://www.twittrapi.com/guides/migrate-from-official-x-api).
