> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xinsider.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination

> Read list results page by page and restart safely when a cursor expires.

To read the next page of a list, send the response's `next_cursor` as the next request's `cursor`. Keep the same filters and stop when `has_more` is `false`.

Treat a cursor as an opaque string: save it, URL-encode it, and send it back unchanged. Do not construct a cursor or depend on its contents.

<span id="the-list-envelope" />

## Read the response

```json theme={null}
{
  "object": "list",
  "data": [],
  "has_more": true,
  "next_cursor": "example_cursor",
  "total": 4210,
  "meta": { "request_id": "req_example", "cached": false, "cost": 1 }
}
```

| Field | Meaning |
| - | - |
| `data` | The rows on this page. |
| `has_more` | Whether another page exists. Use this to decide whether to continue. |
| `next_cursor` | The value for the next page. Most endpoints omit it on the last page; some return `null`. |
| `total` | The full matching row count, only on endpoints that provide one. Explore includes it on the first page only. |

[Event replay](/api-reference/endpoint/get-event-replay-since) keeps a `next_cursor` even when caught up, so you can save it and ask for later events. That does not mean another page exists now.

[Search content](/api-reference/endpoint/search-content), [Webhooks](/api-reference/endpoint/list-webhooks), and [Webhook events](/api-reference/endpoint/list-webhook-events) return one bounded list. They have no cursor parameter and always return `has_more: false`.

<span id="page-size" />

## Choose a page size

| Endpoint | Default `limit` | Maximum `limit` |
| - | - | - |
| Most lists | 20 | 100 |
| [Explore markets](/api-reference/endpoint/explore-markets) | 24 | 48 |
| [Trending wallets](/api-reference/endpoint/list-trending-wallets) | 20 | 50 |
| [Event replay](/api-reference/endpoint/get-event-replay-since) | 50 | 100 |
| [Counterparty executions](/api-reference/endpoint/get-large-trade-counterparty-executions) and [makers](/api-reference/endpoint/get-large-trade-counterparty-makers) | 100 | 100 |

Check the endpoint reference for its exact bounds. Live list endpoints clamp nonnegative whole-number limits into their supported range: `limit=0` becomes `1`, and a value above the maximum becomes the maximum.

`X-Effective-Query` reports the applied limit. A non-integer limit returns `400 invalid_query`; the [sandbox](/sandbox) also rejects numbers outside the schema's bounds.

<span id="treat-the-cursor-as-an-opaque-string" />

## Send a cursor

Let your HTTP client encode the query parameters. With `curl`, use `--data-urlencode`:

```bash theme={null}
curl --get "https://api.0xinsider.com/api/v1/large-trades" \
  -H "Authorization: Bearer $OXINSIDER_API_KEY" \
  --data-urlencode "limit=100" \
  --data-urlencode "cursor=$NEXT_CURSOR"
```

Use a cursor only with the endpoint that issued it, or that endpoint's documented alias. Preserve the filters for the entire run; changing them can invalidate the cursor or change the set you are reading.

<span id="handle-a-400" />

## Recover from an expired cursor

If a request returns `400` with `error.reason` set to `cursor_expired`:

1. Discard the cursor and the rows collected for that run.
2. Request the first page with the same filters.
3. Continue using the new cursors.

No `Retry-After` is sent for this error. Waiting and resending the same cursor will not repair it.

Other `400` responses can indicate a malformed cursor or parameter. Read `error.reason`, `error.param`, and `error.message` before restarting.

<span id="what-ties-a-cursor-to-a-request" />

## Which cursors can expire

Some lists change while you read them. The following cursors are tied to a published ranking, a snapshot, or specific request parameters:

| Endpoint | What must stay valid |
| - | - |
| [Leaderboard](/api-reference/endpoint/get-leaderboard) | The published ranking and the `category` and `strategy` filters. |
| [Trending wallets](/api-reference/endpoint/list-trending-wallets) | The published board, `limit`, and `window`. |
| [Suspicious trades](/api-reference/endpoint/get-suspicious-trades), `mode=stable` | The scoring run, `limit`, `min_suspicion`, and `severity`. |
| [Sharp money flows](/api-reference/endpoint/sharp-money-flows) and its alias | The first page's `as_of`, filters, ranking, and totals. Cursors issued before September 22, 2026 have expired. |
| [Pre-game sides](/api-reference/endpoint/get-pre-game-sides) | The ranking used by the first page, rebuilt about every 180 seconds. |
| [Event replay](/api-reference/endpoint/get-event-replay-since) | `trader`, `condition_id`, `min_grade`, and `min_size`. You can change `expand`. |
| [Counterparty executions](/api-reference/endpoint/get-large-trade-counterparty-executions) and [makers](/api-reference/endpoint/get-large-trade-counterparty-makers) | The trade's `snapshot_id`. Read the trade again if that snapshot expires. |

[Pre-game observations](/api-reference/endpoint/get-pre-game-side-observations) use a signed cursor bound to `snapshot_as_of` and `cohort`. An edited cursor, or one reused for another cohort, returns `400`.

Trade lists, positions, timelines, search, explore, webhook deliveries, and suspicious trades in `mode=live` use row-position cursors rather than the expiring rankings above. A cursor remaining valid does not freeze the underlying data.

Market-holder cursors identify an offset in a shared list and are bound to the market and filters. New cursors allow a different page size; older page-number cursors require their original size. They remain usable after the holder list refreshes.

<span id="read-every-page" />

## Example: read all pages

This Python example restarts after an expired cursor and gives up after 3 attempts:

```python theme={null}
import os
import requests

BASE = "https://api.0xinsider.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['OXINSIDER_API_KEY']}"}

def list_all(path, **filters):
    for _ in range(3):
        items = []
        cursor = None
        while True:
            params = {"limit": 100, **filters}
            if cursor is not None:
                params["cursor"] = cursor
            response = requests.get(
                f"{BASE}{path}", headers=HEADERS, params=params, timeout=40
            )
            body = response.json()
            if (response.status_code == 400
                    and body.get("error", {}).get("reason") == "cursor_expired"):
                break
            response.raise_for_status()
            items.extend(body["data"])
            if not body["has_more"]:
                return items
            cursor = body["next_cursor"]
            if not cursor:
                raise RuntimeError("A page with has_more=true needs next_cursor")
    raise RuntimeError("The cursor expired on all 3 attempts")

trades = list_all("/large-trades", min_grade="A")
```

<span id="what-this-does-not-tell-you" />

## Limits

Most lists do not return a total count. A valid cursor does not promise an unchanged ranking, and there is no page-number shortcut to later pages.

This handles pagination only. Add your application's [rate-limit handling](/rate-limits) before using it for a long-running job.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.