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

# Pagination

> Navigate large result sets using cursor-based pagination across all Zenoo API list endpoints.

# Pagination

All Zenoo API list endpoints use cursor-based pagination. This provides stable, performant pagination even on large datasets with concurrent modifications.

## Request format

| Parameter | Type   | Default | Description                              |
| --------- | ------ | ------- | ---------------------------------------- |
| `cursor`  | string | null    | Pagination cursor from previous response |
| `limit`   | number | 25      | Results per page (min 1, max 100)        |

```bash theme={null}
# First page
curl -X GET "https://api.zenoo.com/v1/alerts?status=Open&limit=25" \
  -H "Authorization: Bearer your-api-key"

# Next page
curl -X GET "https://api.zenoo.com/v1/alerts?status=Open&limit=25&cursor=eyJpZCI6ImFsdF8wMjUiLCJzIjoiMjAyNi0wMi0xNlQxMDowMDowMFoifQ" \
  -H "Authorization: Bearer your-api-key"
```

## Response format

Every list response includes a `meta.page` object:

```json theme={null}
{
  "data": [
    { "token": "alt_001", "title": "PEP Match: John Doe", "status": "Open" },
    {
      "token": "alt_002",
      "title": "Sanctions Hit: Acme Corp",
      "status": "Open"
    }
  ],
  "meta": {
    "total": 142,
    "page": {
      "cursor": "eyJpZCI6ImFsdF8wMjUiLCJzIjoiMjAyNi0wMi0xNlQxMDowMDowMFoifQ",
      "has_more": true
    }
  }
}
```

| Field                | Type    | Description                                                       |
| -------------------- | ------- | ----------------------------------------------------------------- |
| `meta.total`         | number  | Total count of records matching the query                         |
| `meta.page.cursor`   | string  | Opaque cursor to pass for the next page. `null` if no more pages. |
| `meta.page.has_more` | boolean | Whether more results exist beyond this page                       |

## Full iteration example

<CodeGroup>
  ```python Python theme={null}
  import requests

  def get_all_alerts(status="Open"):
      url = "https://api.zenoo.com/v1/alerts"
      headers = {"Authorization": "Bearer your-api-key"}
      params = {"status": status, "limit": 100}
      all_alerts = []

      while True:
          response = requests.get(url, headers=headers, params=params)
          data = response.json()
          all_alerts.extend(data["data"])

          if not data["meta"]["page"]["has_more"]:
              break

          params["cursor"] = data["meta"]["page"]["cursor"]

      return all_alerts

  alerts = get_all_alerts()
  print(f"Total: {len(alerts)} alerts")
  ```

  ```javascript Node.js theme={null}
  async function getAllAlerts(status = "Open") {
    const headers = { Authorization: "Bearer your-api-key" };
    const allAlerts = [];
    let cursor = null;

    do {
      const params = new URLSearchParams({ status, limit: "100" });
      if (cursor) params.set("cursor", cursor);

      const response = await fetch(`https://api.zenoo.com/v1/alerts?${params}`, {
        headers
      });
      const data = await response.json();
      allAlerts.push(...data.data);

      cursor = data.meta.page.has_more ? data.meta.page.cursor : null;
    } while (cursor);

    return allAlerts;
  }

  const alerts = await getAllAlerts();
  console.log(`Total: ${alerts.length} alerts`);
  ```

  ```bash curl (loop) theme={null}
  CURSOR=""
  PAGE=1

  while true; do
    if [ -z "$CURSOR" ]; then
      RESPONSE=$(curl -s "https://api.zenoo.com/v1/alerts?status=Open&limit=25" \
        -H "Authorization: Bearer your-api-key")
    else
      RESPONSE=$(curl -s "https://api.zenoo.com/v1/alerts?status=Open&limit=25&cursor=$CURSOR" \
        -H "Authorization: Bearer your-api-key")
    fi

    echo "Page $PAGE: $(echo $RESPONSE | jq '.data | length') results"

    HAS_MORE=$(echo $RESPONSE | jq -r '.meta.page.has_more')
    if [ "$HAS_MORE" = "false" ]; then
      break
    fi

    CURSOR=$(echo $RESPONSE | jq -r '.meta.page.cursor')
    PAGE=$((PAGE + 1))
  done
  ```
</CodeGroup>

## Sorting and filtering

Cursors are stable across sort orders and filters. You can combine pagination with any sort or filter parameter:

```bash theme={null}
# Paginate open alerts sorted by priority (descending)
curl -X GET "https://api.zenoo.com/v1/alerts?status=Open&sort=-priority_score&limit=25&cursor=eyJ..." \
  -H "Authorization: Bearer your-api-key"

# Paginate case audit logs filtered by date
curl -X GET "https://api.zenoo.com/v1/cases/cas_xyz789/audit-logs?from=2026-01-01T00:00:00Z&limit=50&cursor=eyJ..." \
  -H "Authorization: Bearer your-api-key"
```

<Warning>
  Do not modify the sort or filter parameters between paginated requests. Changing filters while iterating may cause duplicate or missing results. Start a new pagination sequence if you need different filters.
</Warning>

## Endpoints supporting pagination

| Endpoint                           | Default limit | Max limit |
| ---------------------------------- | ------------- | --------- |
| `GET /v1/alerts`                   | 25            | 100       |
| `GET /v1/cases`                    | 25            | 100       |
| `GET /v1/cases/{token}/alerts`     | 25            | 100       |
| `GET /v1/cases/{token}/checks`     | 25            | 100       |
| `GET /v1/cases/{token}/entities`   | 25            | 100       |
| `GET /v1/cases/{token}/audit-logs` | 50            | 200       |
| `GET /v1/cases/{token}/comments`   | 25            | 100       |
| `GET /v1/entities`                 | 25            | 100       |
| `GET /v1/audit-logs`               | 50            | 200       |

## Why cursor-based pagination

Cursor-based pagination has advantages over offset-based (`?page=2&per_page=25`) pagination:

| Aspect          | Cursor-based                            | Offset-based                        |
| --------------- | --------------------------------------- | ----------------------------------- |
| **Performance** | O(1) -- seeks directly to position      | O(n) -- scans and skips rows        |
| **Consistency** | Stable under concurrent inserts/deletes | Rows shift, causing duplicates/gaps |
| **Deep pages**  | Same speed for page 1 and page 1000     | Degrades linearly with offset       |

## Next steps

* [Alert Review Quickstart](/use-cases/alert-review). Paginated alert listing in action.
* [Bulk Operations](/guides/bulk-operations). Process multiple records in a single request.
