# Pagination

BillerAPI list endpoints use cursor-based pagination. This provides stable, efficient pagination even as data changes.

## How It Works

List endpoints return a page of results along with pagination metadata. Use the `next_cursor` value from the response to fetch the next page. The first request needs no cursor — every list response includes `next_cursor` and `has_more`, so you can start paginating from any plain list call.

### Request Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| limit | integer | 100 | Number of items per page. Min 1, max 500. |
| cursor | string | null | Opaque cursor from a previous response. Omit for the first page. |

### Response Fields

The items array is named after the resource — `bills` on `GET /v1/bills`, `billers` on `GET /v1/billers`, `insights` on `GET /v1/insights`.

| Field | Type | Description |
| --- | --- | --- |
| `<resource>` | array | The list of items for the current page, keyed by the resource name (e.g. `bills`). |
| has_more | boolean | Whether there are additional pages after this one. |
| next_cursor | string | Cursor to pass in the next request. Empty when there are no more pages. |
| total_count | integer | Total number of items matching the query, across all pages. |

## Example Response

**JSON**

```json
{
  "bills": [
    { "id": "bill_abc123", "amount": 127.50, "status": "PENDING" },
    { "id": "bill_def456", "amount": 89.99, "status": "PAID" }
  ],
  "total_count": 12,
  "has_more": true,
  "next_cursor": "eyJsYXN0X2lkIjoiYmlsbF9kZWY0NTYifQ=="
}
```

## Paginating Through Results

Loop until `has_more` is `false` to fetch all pages.

**Fetch all pages**

**JavaScript**

```javascript
async function fetchAllBills(apiKey, accountLinkId) {
  const bills = [];
  let cursor = null;

  do {
    const params = new URLSearchParams({
      account_link_id: accountLinkId,
      limit: '100',
    });
    if (cursor) params.set('cursor', cursor);

    const response = await fetch(
      `https://sandbox.api.billerapi.com/v1/bills?${params}`,
      { headers: { 'Authorization': `Bearer ${apiKey}` } }
    );
    const page = await response.json();

    bills.push(...page.bills);
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);

  return bills;
}
```

**Python**

```python
def fetch_all_bills(api_key, account_link_id):
    bills = []
    cursor = None

    while True:
        params = {'account_link_id': account_link_id, 'limit': 100}
        if cursor:
            params['cursor'] = cursor

        response = requests.get(
            'https://sandbox.api.billerapi.com/v1/bills',
            headers={'Authorization': f'Bearer {api_key}'},
            params=params,
        )
        page = response.json()

        bills.extend(page['bills'])

        if not page['has_more']:
            break
        cursor = page['next_cursor']

    return bills
```

## Notes

- Cursors are opaque strings. Do not parse or construct them — always use the value returned by the API.
- The default page size is 100 items. You can request up to 500 items per page using the `limit` parameter.
- Every list endpoint uses the same `limit` + `cursor` contract. There is no page-based alternative: sending `page` or `page_size` returns `400`.

## Related

- [API Reference: Bills](/docs/api/bills) — paginated bill retrieval
- [API Reference: Billers](/docs/api/billers) — paginated biller search
- [Rate Limits](/docs/concepts/rate-limits) — request limits per minute
