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

# Pagination

> Learn how cursor and page_size work together to paginate list endpoints in the v2 API

## Why cursor-based pagination

Cursor-based pagination returns a small, ordered slice of results plus an opaque token you pass back to get the next slice. Compared to offset-based pagination, cursors are typically more consistent and performant under concurrent writes and large datasets.

<Info>
  Use the `cursor` returned by the API as-is. Do not build or parse cursors on the client.
</Info>

## How it works

* Request parameters
  * `page_size` — number of items per page. Typical range is 1–100 with default 10. Some endpoints may enforce different bounds; check the API Reference for specifics.
  * `cursor` — pass the string returned in `paging.next_cursor` from the previous response to load the next page.
* Response fields
  * `paging.next_cursor` — present only when there is more data to fetch. If it’s missing or empty, you’ve reached the end.

<Tip>
  Keep all filters and sort parameters identical for every page in a pagination sequence. Changing them mid-stream can lead to duplicates or gaps.
</Tip>

## Example: Validators list

First page (no `cursor`):

```bash theme={null}
curl --request POST \
  --url https://beaconcha.in/api/v2/ethereum/validators \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "chain": "mainnet",
  "page_size": 10,
  "validator": {
    "validator_identifiers": [
      1
    ]
  }
}
'
```

Response (excerpt):

```json theme={null}
{
  "data": [
    { /* validator item */ },
    { /* validator item */ },
    { /* validator item */ }
  ],
  "paging": {
    "next_cursor": "eyJfc2FtcGxlX2N1cnNvciI6MTIzNDU2Nzg5MH0" // truncated
  }
}
```

Next page (use `paging.next_cursor` from the previous response):

```bash theme={null}
curl --request POST \
  --url https://beaconcha.in/api/v2/ethereum/validators \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "chain": "mainnet",
  "page_size": 10,
  "cursor": "eyJfc2FtcGxlX2N1cnNvciI6MTIzNDU2Nzg5MH0",
  "validator": {
    "validator_identifiers": [
      1
    ]
  }
}
'
```

Response (excerpt):

```json theme={null}
{
  "data": [ /* next 3 validators */ ],
  "paging": {
    "next_cursor": "eyJfc2FtcGxlX2N1cnNvciI6OTg3NjU0MzIxMH0" // if more data remains
  }
}
```

<Note>
  Some endpoints require additional filters (e.g., validator selectors or time ranges). Include the same filters on every paginated request. Refer to the API Reference for each endpoint’s required and optional parameters.
</Note>

## Complete Pagination Examples

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import requests
    from typing import Iterator, Any

    API_KEY = "<YOUR_API_KEY>"
    BASE_URL = "https://beaconcha.in"

    def paginate_all(endpoint: str, payload: dict, page_size: int = 100) -> Iterator[dict]:
        """
        Generator that yields all items across all pages.
        
        Args:
            endpoint: API endpoint path
            payload: Request payload (without cursor)
            page_size: Items per page (max 100)
        
        Yields:
            Individual items from each page
        """
        cursor = None
        payload = {**payload, "page_size": page_size}
        
        while True:
            if cursor:
                payload["cursor"] = cursor
            
            response = requests.post(
                f"{BASE_URL}{endpoint}",
                headers={
                    "Authorization": f"Bearer {API_KEY}",
                    "Content-Type": "application/json"
                },
                json=payload
            )
            response.raise_for_status()
            result = response.json()
            
            # Yield each item
            for item in result.get("data", []):
                yield item
            
            # Check for next page
            cursor = result.get("paging", {}).get("next_cursor")
            if not cursor:
                break  # No more pages

    # Usage: Get all rewards for validators
    all_rewards = list(paginate_all(
        "/api/v2/ethereum/validators/rewards-list",
        {
            "chain": "mainnet",
            "validator": {"validator_identifiers": [1, 2, 3]},
            "epoch": 347566
        }
    ))
    print(f"Retrieved {len(all_rewards)} reward records")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const API_KEY = "<YOUR_API_KEY>";
    const BASE_URL = "https://beaconcha.in";

    interface PaginatedResponse<T> {
      data: T[];
      paging: { next_cursor?: string };
    }

    async function* paginateAll<T>(
      endpoint: string,
      payload: Record<string, unknown>,
      pageSize: number = 100
    ): AsyncGenerator<T> {
      let cursor: string | undefined;
      const requestPayload = { ...payload, page_size: pageSize };

      while (true) {
        if (cursor) {
          requestPayload.cursor = cursor;
        }

        const response = await fetch(`${BASE_URL}${endpoint}`, {
          method: "POST",
          headers: {
            "Authorization": `Bearer ${API_KEY}`,
            "Content-Type": "application/json"
          },
          body: JSON.stringify(requestPayload)
        });

        if (!response.ok) {
          const error = await response.json();
          throw new Error(`API error ${response.status}: ${error.error}`);
        }

        const result: PaginatedResponse<T> = await response.json();

        // Yield each item
        for (const item of result.data) {
          yield item;
        }

        // Check for next page
        cursor = result.paging?.next_cursor;
        if (!cursor) break;
      }
    }

    // Usage: Get all rewards for validators
    async function getAllRewards() {
      const rewards = [];
      for await (const reward of paginateAll(
        "/api/v2/ethereum/validators/rewards-list",
        {
          chain: "mainnet",
          validator: { validator_identifiers: [1, 2, 3] },
          epoch: 347566
        }
      )) {
        rewards.push(reward);
      }
      console.log(`Retrieved ${rewards.length} reward records`);
      return rewards;
    }
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const API_KEY = "<YOUR_API_KEY>";
    const BASE_URL = "https://beaconcha.in";

    async function* paginateAll(endpoint, payload, pageSize = 100) {
      let cursor = undefined;
      const requestPayload = { ...payload, page_size: pageSize };

      while (true) {
        if (cursor) {
          requestPayload.cursor = cursor;
        }

        const response = await fetch(`${BASE_URL}${endpoint}`, {
          method: "POST",
          headers: {
            "Authorization": `Bearer ${API_KEY}`,
            "Content-Type": "application/json"
          },
          body: JSON.stringify(requestPayload)
        });

        if (!response.ok) {
          const error = await response.json();
          throw new Error(`API error ${response.status}: ${error.error}`);
        }

        const result = await response.json();

        // Yield each item
        for (const item of result.data) {
          yield item;
        }

        // Check for next page
        cursor = result.paging?.next_cursor;
        if (!cursor) break;
      }
    }

    // Usage: Get all rewards for validators
    async function getAllRewards() {
      const rewards = [];
      for await (const reward of paginateAll(
        "/api/v2/ethereum/validators/rewards-list",
        {
          chain: "mainnet",
          validator: { validator_identifiers: [1, 2, 3] },
          epoch: 347566
        }
      )) {
        rewards.push(reward);
      }
      console.log(`Retrieved ${rewards.length} reward records`);
      return rewards;
    }
    ```
  </Tab>
</Tabs>

## Endpoint-specific limits

While many endpoints accept `page_size` values between 1 and 100 (default 10), some may enforce different limits or include additional paging fields. Always check the API Reference for the exact bounds and response schema of each endpoint.

<Tip>
  If you need a stable snapshot across many pages, keep your filters constant and paginate in one continuous session.
</Tip>
