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

# Pagination

> Cursor pagination for list endpoints.

List endpoints (e.g. `GET /v1/customers`) use **cursor pagination**. No
offset, no page number — cursors are deterministic under writes and
won't skip or duplicate records when new customers are ingested
mid-walk.

## How it works

```http theme={null}
GET /v1/customers?limit=50
```

```json theme={null}
{
  "object": "list",
  "data": [ /* up to 50 customers */ ],
  "has_more": true,
  "next_cursor": "eyJjcmVhdGVkQXQiOiIyMDI2LTA2LTI0VDAwOjAwOjAwLjAwMFoiLCJpZCI6IjU1MGU4NDAwLS4uIn0",
  "limit": 50
}
```

| Field         | Type           | Meaning                                                                             |
| ------------- | -------------- | ----------------------------------------------------------------------------------- |
| `object`      | string         | Always `"list"`. Lets you distinguish list envelopes from single-record responses.  |
| `data`        | array          | The page of records, never `null`. Empty when no records match.                     |
| `has_more`    | boolean        | `true` if at least one more page exists. Use this as the loop condition.            |
| `next_cursor` | string \| null | Opaque cursor to pass back. `null` on the last page (mirrors `has_more === false`). |
| `limit`       | integer        | The limit echoed back (after defaulting/clamping).                                  |
| `total_count` | integer        | Only present when `include=total_count` was requested. See below.                   |

Pass `next_cursor` back as `cursor` on the next request:

```http theme={null}
GET /v1/customers?limit=50&cursor=eyJjcmVhdGVkQXQiOiI...
```

When you reach the end, `has_more` is `false` and `next_cursor` is `null`.

## Limit

|         | Min | Default | Max |
| ------- | --- | ------- | --- |
| `limit` | 1   | 50      | 100 |

## What's in a cursor

The cursor is **opaque** — base64url-encoded JSON containing the last
row's `(createdAt, id)`. Don't parse or generate cursors yourself; pass
them back as-is.

## Combining with filters

Filters work alongside cursors. The cursor sticks to the filter set
from the original request — don't change filters mid-walk:

```http theme={null}
GET /v1/customers?status=IN_PROGRESS&aml_status=NEEDS_REVIEW&limit=100
```

## Sample walk loop

```javascript theme={null}
async function* walkAllCustomers(filters = {}) {
  let cursor = null;
  while (true) {
    const url = new URL('https://app.instantcompliance.ai/api/v1/customers');
    Object.entries(filters).forEach(([k, v]) => url.searchParams.set(k, v));
    url.searchParams.set('limit', '100');
    if (cursor) url.searchParams.set('cursor', cursor);

    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.IC_API_KEY}` }
    });
    const { data, has_more, next_cursor } = await res.json();
    for (const customer of data) yield customer;
    if (!has_more) break;
    cursor = next_cursor;
  }
}
```

## Total count (opt-in)

For UIs that want to render "Showing 50 of 1,283" or for sizing a
background job, request `include=total_count`:

```http theme={null}
GET /v1/customers?limit=50&include=total_count
```

```json theme={null}
{
  "object": "list",
  "data": [ /* … */ ],
  "has_more": true,
  "next_cursor": "eyJjcmVhdGVkQXQiOiI…",
  "limit": 50,
  "total_count": 1283
}
```

`total_count` reflects the **full filter set**, not the current page —
walking forward does not shrink it. It costs an extra `COUNT(*)` query
per call, so don't request it on hot polling loops where you don't need
the number. If `include` is omitted, `total_count` is not returned at
all (rather than `null`) so consumers can detect "did I ask for it?"
unambiguously.

## Polling for changes

For "what changed since I last looked?" use the `updated_since` filter
alongside pagination. See [Polling for status](/guides/polling-status)
for a robust loop.
