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

# Pagination

> Navigate large result sets using cursor-based pagination

# Pagination

Ticksupply uses cursor-based pagination for endpoints that return lists of items. This approach provides stable, consistent results even when data changes between requests.

## How pagination works

Paginated endpoints accept two query parameters:

| Parameter    | Type    | Description                                       |
| ------------ | ------- | ------------------------------------------------- |
| `limit`      | integer | Number of items per page (see limits below)       |
| `page_token` | string  | Cursor for the next page (from previous response) |

Default limits vary by endpoint:

| Endpoints                | Default | Max   |
| ------------------------ | ------- | ----- |
| Subscriptions, Exports   | 50      | 100   |
| Instruments, Datastreams | 100     | 1,000 |

The response includes pagination metadata:

```json theme={"system"}
{
  "items": [...],
  "total": 150,
  "limit": 50,
  "next_page_token": "eyJpZCI6IjEyMzQ1IiwidHMiOiIyMDI0LTEyLTIxIn0="
}
```

| Field             | Description                                     |
| ----------------- | ----------------------------------------------- |
| `items`           | Array of results for the current page           |
| `total`           | Total number of items across all pages          |
| `limit`           | Items per page (as requested)                   |
| `next_page_token` | Token for the next page (null if no more pages) |

## Basic pagination

### First page

Request the first page without a `page_token`:

```bash theme={"system"}
curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://api.ticksupply.com/v1/subscriptions?limit=10"
```

Response:

```json theme={"system"}
{
  "items": [
    {"id": "sub_0194a1b2c3d47e5fa7b8c9d0e1f2a3b4", "status": "active", ...},
    {"id": "sub_0194b2c3d4e57f6ab8c9d0e1f2a3b4c5", "status": "active", ...},
    // ... 8 more items
  ],
  "total": 47,
  "limit": 10,
  "next_page_token": "eyJ0cyI6IjIwMjQtMTItMjFUMTI6MDA6MDBaIiwiaWQiOiIwMTk0YTFiMi1jM2Q0LTdlNWYtYTdiOC1jOWQwZTFmMmEzYjQifQ"
}
```

### Subsequent pages

Use the `next_page_token` from the previous response:

```bash theme={"system"}
curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://api.ticksupply.com/v1/subscriptions?limit=10&page_token=eyJ0cyI6IjIwMjQtMTItMjFUMTI6MDA6MDBaIiwiaWQiOiIwMTk0YTFiMi1jM2Q0LTdlNWYtYTdiOC1jOWQwZTFmMmEzYjQifQ"
```

### Last page

When there are no more items, `next_page_token` is `null`:

```json theme={"system"}
{
  "items": [
    {"id": "sub_0194c3d4e5f6708a9bcadb1e2f3a4b5c", "status": "paused", ...},
    // ... remaining items
  ],
  "total": 47,
  "limit": 10,
  "next_page_token": null
}
```

## Complete pagination example

<CodeGroup>
  ```python Python theme={"system"}
  def fetch_all_subscriptions():
      """Fetch all subscriptions using pagination."""
      all_subscriptions = []
      page_token = None
      
      while True:
          params = {"limit": 100}
          if page_token:
              params["page_token"] = page_token
          
          response = requests.get(
              "https://api.ticksupply.com/v1/subscriptions",
              headers={"X-Api-Key": API_KEY},
              params=params
          )
          response.raise_for_status()
          data = response.json()
          
          all_subscriptions.extend(data["items"])
          print(f"Fetched {len(all_subscriptions)}/{data['total']} subscriptions")
          
          page_token = data.get("next_page_token")
          if not page_token:
              break
      
      return all_subscriptions

  # Usage
  subscriptions = fetch_all_subscriptions()
  print(f"Total: {len(subscriptions)} subscriptions")
  ```

  ```javascript JavaScript theme={"system"}
  async function fetchAllSubscriptions() {
    const allSubscriptions = [];
    let pageToken = null;
    
    while (true) {
      const params = new URLSearchParams({ limit: "100" });
      if (pageToken) {
        params.set("page_token", pageToken);
      }
      
      const response = await fetch(
        `https://api.ticksupply.com/v1/subscriptions?${params}`,
        { headers: { "X-Api-Key": API_KEY } }
      );
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      const data = await response.json();
      allSubscriptions.push(...data.items);
      
      console.log(`Fetched ${allSubscriptions.length}/${data.total} subscriptions`);
      
      pageToken = data.next_page_token;
      if (!pageToken) break;
    }
    
    return allSubscriptions;
  }

  // Usage
  const subscriptions = await fetchAllSubscriptions();
  console.log(`Total: ${subscriptions.length} subscriptions`);
  ```
</CodeGroup>

## Generator pattern

For memory efficiency with large datasets, use a generator pattern:

<CodeGroup>
  ```python Python theme={"system"}
  def iter_subscriptions(limit=100):
      """Iterate through subscriptions one at a time."""
      page_token = None
      
      while True:
          params = {"limit": limit}
          if page_token:
              params["page_token"] = page_token
          
          response = requests.get(
              "https://api.ticksupply.com/v1/subscriptions",
              headers={"X-Api-Key": API_KEY},
              params=params
          )
          response.raise_for_status()
          data = response.json()
          
          for item in data["items"]:
              yield item
          
          page_token = data.get("next_page_token")
          if not page_token:
              break

  # Usage - processes one item at a time
  for subscription in iter_subscriptions():
      process_subscription(subscription)
  ```

  ```javascript JavaScript theme={"system"}
  async function* iterSubscriptions(limit = 100) {
    let pageToken = null;
    
    while (true) {
      const params = new URLSearchParams({ limit: String(limit) });
      if (pageToken) {
        params.set("page_token", pageToken);
      }
      
      const response = await fetch(
        `https://api.ticksupply.com/v1/subscriptions?${params}`,
        { headers: { "X-Api-Key": API_KEY } }
      );
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      const data = await response.json();
      
      for (const item of data.items) {
        yield item;
      }
      
      pageToken = data.next_page_token;
      if (!pageToken) break;
    }
  }

  // Usage - processes one item at a time
  for await (const subscription of iterSubscriptions()) {
    await processSubscription(subscription);
  }
  ```
</CodeGroup>

## Cursor stability

Cursor-based pagination provides stable results:

<AccordionGroup>
  <Accordion title="Items aren't skipped or duplicated">
    Unlike offset-based pagination, cursor pagination doesn't skip items when new data is added or duplicates items when data is deleted.
  </Accordion>

  <Accordion title="Consistent ordering">
    Results are ordered by creation time (newest first) and ID. This ordering remains stable across pages.
  </Accordion>

  <Accordion title="Cursors are opaque">
    Treat page tokens as opaque strings. Don't parse, modify, or construct them—only use tokens returned by the API.
  </Accordion>
</AccordionGroup>

<Warning>
  Page tokens may expire after extended periods. If you receive an error with an old token, start from the first page.
</Warning>

## Best practices

### Use appropriate page sizes

* **Small datasets**: Use default limits or smaller for quick responses
* **Bulk operations**: Use maximum limit (100 for subscriptions/exports, 1,000 for catalog) to reduce API calls
* **UI pagination**: Match your UI's display capacity

```python theme={"system"}
# For listing in UI
response = requests.get(url, params={"limit": 20})

# For bulk processing
response = requests.get(url, params={"limit": 100})
```

### Handle rate limits during pagination

When paginating large datasets, implement rate limit handling:

```python theme={"system"}
def fetch_all_with_rate_limit(endpoint, limit=100):
    all_items = []
    page_token = None
    
    while True:
        params = {"limit": limit}
        if page_token:
            params["page_token"] = page_token
        
        response = requests.get(
            endpoint,
            headers={"X-Api-Key": API_KEY},
            params=params
        )
        
        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 30))
            print(f"Rate limited, waiting {retry_after}s...")
            time.sleep(retry_after)
            continue
        
        response.raise_for_status()
        data = response.json()
        
        all_items.extend(data["items"])
        page_token = data.get("next_page_token")
        
        if not page_token:
            break
    
    return all_items
```

### Don't store page tokens long-term

Page tokens are meant for immediate pagination, not long-term storage:

```python theme={"system"}
# ✅ Good: Use immediately
data = fetch_page_1()
data_2 = fetch_page_2(data["next_page_token"])

# ❌ Bad: Store for later
save_to_database(data["next_page_token"])  # May expire
```

## Paginated endpoints

| Endpoint                                                            | Default limit | Max limit |
| ------------------------------------------------------------------- | ------------- | --------- |
| `GET /v1/subscriptions`                                             | 50            | 100       |
| `GET /v1/exports`                                                   | 50            | 100       |
| `GET /v1/exchanges/{exchange}/instruments`                          | 100           | 1,000     |
| `GET /v1/datastreams`                                               | 100           | 1,000     |
| `GET /v1/exchanges/{exchange}/datastreams`                          | 100           | 1,000     |
| `GET /v1/exchanges/{exchange}/instruments/{instrument}/datastreams` | 100           | 1,000     |

## Next steps

<CardGroup cols={2}>
  <Card title="Error Handling" icon="circle-exclamation" href="/guides/error-handling">
    Handle API errors gracefully in your application
  </Card>

  <Card title="Rate Limiting" icon="gauge" href="/guides/rate-limiting">
    Understand rate limits and how to handle them
  </Card>

  <Card title="Idempotency" icon="rotate" href="/guides/idempotency">
    Safely retry requests using idempotency keys
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/overview">
    Explore the complete API documentation
  </Card>
</CardGroup>
