Skip to main content

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.
Use the cursor returned by the API as-is. Do not build or parse cursors on the client.

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

Example: Validators list

First page (no cursor):
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):
{
  "data": [
    { /* validator item */ },
    { /* validator item */ },
    { /* validator item */ }
  ],
  "paging": {
    "next_cursor": "eyJfc2FtcGxlX2N1cnNvciI6MTIzNDU2Nzg5MH0" // truncated
  }
}
Next page (use paging.next_cursor from the previous response):
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):
{
  "data": [ /* next 3 validators */ ],
  "paging": {
    "next_cursor": "eyJfc2FtcGxlX2N1cnNvciI6OTg3NjU0MzIxMH0" // if more data remains
  }
}
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.

Complete Pagination Examples

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")

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.
If you need a stable snapshot across many pages, keep your filters constant and paginate in one continuous session.