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

# Pagination

> How to paginate through reviewed public list responses

Reviewed public list endpoints in the Zentra API return paginated results to keep response times predictable and payloads bounded, but the query style can vary by resource.

## How It Works

Transfers and virtual accounts currently document `page` plus `limit`:

```bash theme={null}
GET /api/v1/transfers?page=2&limit=50
```

Cards currently document `limit` plus `offset`:

```bash theme={null}
GET /api/v1/cards?limit=50&offset=50
```

### Parameters

| Parameter | Type    | Default | Max | Description                                                     |
| --------- | ------- | ------- | --- | --------------------------------------------------------------- |
| `page`    | integer | 1       | -   | Page number for resources that expose page-based pagination     |
| `limit`   | integer | 20      | 100 | Results per page                                                |
| `offset`  | integer | 0       | -   | Record offset for resources that expose offset-based pagination |

## Response Format

Paginated responses include a `pagination` object with metadata:

```json theme={null}
{
  "success": true,
  "data": [
    {
      "id": "trn_123",
      "amount_minor": 10000,
      "status": "completed"
    }
  ],
  "pagination": {
    "page": 2,
    "limit": 50,
    "total": 243,
    "pages": 5,
    "has_more": true
  }
}
```

### Pagination Object

| Field      | Type    | Description                  |
| ---------- | ------- | ---------------------------- |
| `page`     | integer | Current page number          |
| `limit`    | integer | Results per page             |
| `total`    | integer | Total number of items        |
| `pages`    | integer | Total number of pages        |
| `has_more` | boolean | Whether there are more pages |

## Examples

### Fetching First Transfer Page

<CodeGroup>
  ```javascript JavaScript theme={null}
  const transfers = await client.transfers.list({
    page: 1,
    limit: 20
  });

  console.log(`Page ${transfers.pagination.page} of ${transfers.pagination.pages}`);
  console.log(`Total: ${transfers.pagination.total} transfers`);
  ```

  ```python Python theme={null}
  transfers = client.transfers.list(
      page=1,
      limit=20
  )

  print(f"Page {transfers.pagination.page} of {transfers.pagination.pages}")
  print(f"Total: {transfers.pagination.total} transfers")
  ```

  ```php PHP theme={null}
  $transfers = $client->transfers->list([
      'page' => 1,
      'limit' => 20
  ]);

  echo "Page {$transfers->pagination->page} of {$transfers->pagination->pages}";
  echo "Total: {$transfers->pagination->total} transfers";
  ```

  ```bash cURL theme={null}
  curl "https://api.usezentra.com/api/v1/transfers?page=1&limit=20" \
    -H "Authorization: Bearer YOUR_SECRET_KEY"
  ```
</CodeGroup>

### Fetching All Transfer Pages

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function fetchAllTransfers() {
    const allTransfers = [];
    let page = 1;
    let hasMore = true;

    while (hasMore) {
      const response = await client.transfers.list({
        page,
        limit: 100
      });

      allTransfers.push(...response.data);
      hasMore = response.pagination.has_more;
      page++;
    }

    return allTransfers;
  }

  const all = await fetchAllTransfers();
  console.log(`Fetched ${all.length} transfers`);
  ```

  ```python Python theme={null}
  def fetch_all_transfers():
      all_transfers = []
      page = 1
      has_more = True

      while has_more:
          response = client.transfers.list(
              page=page,
              limit=100
          )

          all_transfers.extend(response.data)
          has_more = response.pagination.has_more
          page += 1

      return all_transfers

  all_transfers = fetch_all_transfers()
  print(f"Fetched {len(all_transfers)} transfers")
  ```

  ```php PHP theme={null}
  function fetchAllTransfers($client) {
      $allTransfers = [];
      $page = 1;
      $hasMore = true;

      while ($hasMore) {
          $response = $client->transfers->list([
              'page' => $page,
              'limit' => 100
          ]);

          $allTransfers = array_merge($allTransfers, $response->data);
          $hasMore = $response->pagination->has_more;
          $page++;
      }

      return $allTransfers;
  }

  $all = fetchAllTransfers($client);
  echo "Fetched " . count($all) . " transfers";
  ```
</CodeGroup>

## Filtering with Pagination

Combine pagination with filters for more specific queries:

```bash theme={null}
GET /api/v1/transfers?status=successful&page=2&limit=50
GET /api/v1/transfers?start_date=2024-01-01&end_date=2024-01-31&page=1&limit=100
```

<CodeGroup>
  ```javascript JavaScript theme={null}
  const completedTransfers = await client.transfers.list({
    status: 'completed',
    start_date: '2024-01-01',
    end_date: '2024-01-31',
    page: 1,
    limit: 50
  });
  ```

  ```python Python theme={null}
  completed = client.transfers.list(
      status='completed',
      start_date='2024-01-01',
      end_date='2024-01-31',
      page=1,
      limit=50
  )
  ```

  ```php PHP theme={null}
  $completed = $client->transfers->list([
      'status' => 'completed',
      'start_date' => '2024-01-01',
      'end_date' => '2024-01-31',
      'page' => 1,
      'limit' => 50
  ]);
  ```
</CodeGroup>

## Sorting

Sorting support varies by resource. For example, reviewed virtual account listing supports:

```bash theme={null}
GET /api/v1/virtual-accounts?sort=-created_at
GET /api/v1/virtual-accounts?sort=account_number
GET /api/v1/virtual-accounts?sort=-total_received_minor
```

<CodeGroup>
  ```javascript JavaScript theme={null}
  const accounts = await client.virtualAccounts.list({
    sort: '-created_at',
    limit: 50
  });

  const highestInbound = await client.virtualAccounts.list({
    sort: '-total_received_minor',
    limit: 50
  });
  ```

  ```python Python theme={null}
  accounts = client.virtual_accounts.list(
      sort='-created_at',
      limit=50
  )

  highest_inbound = client.virtual_accounts.list(
      sort='-total_received_minor',
      limit=50
  )
  ```

  ```php PHP theme={null}
  $accounts = $client->virtualAccounts->list([
      'sort' => '-created_at',
      'limit' => 50
  ]);

  $highestInbound = $client->virtualAccounts->list([
      'sort' => '-total_received_minor',
      'limit' => 50
  ]);
  ```
</CodeGroup>

## Performance Tips

<AccordionGroup>
  <Accordion title="Use Maximum Limit">
    When fetching multiple pages, use `limit=100` to minimize the number of requests.
  </Accordion>

  <Accordion title="Filter First">
    Apply filters to reduce the dataset before paginating.
  </Accordion>

  <Accordion title="Cache Results">
    Cache paginated results when possible to avoid repeated API calls.
  </Accordion>

  <Accordion title="Use Webhooks">
    Instead of polling for new data, use [webhooks](/api-reference/webhooks/overview) for real-time updates.
  </Accordion>

  <Accordion title="Avoid Deep Pagination">
    Fetching very high page numbers can be slow. Prefer date-range filters or incremental sync patterns where supported.
  </Accordion>
</AccordionGroup>

## Cursor-Based Pagination

Cursor-based pagination is not currently part of the reviewed public gateway contract. If Zentra introduces it later, it will be documented as a separate reviewed route shape instead of being implied by the current page-and-limit contract.

## Reviewed List Endpoints

The following reviewed public endpoints currently document paginated list behavior:

| Endpoint                         | Resource           | Query Style                                                      |
| -------------------------------- | ------------------ | ---------------------------------------------------------------- |
| `GET /api/v1/transfers`          | Transfers          | `page`, `limit`                                                  |
| `GET /api/v1/virtual-accounts`   | Virtual Accounts   | `page`, `limit`, optional `offset`                               |
| `GET /api/v1/cards`              | Cards              | `limit`, `offset`                                                |
| `GET /api/v1/identity/customers` | Identity Customers | tenant-specific contract details may narrow the exact filter set |

## Common Patterns

### Infinite Scroll

```javascript theme={null}
let page = 1;
let isLoading = false;

async function loadMore() {
  if (isLoading) return;

  isLoading = true;
  const response = await client.transfers.list({ page, limit: 20 });

  appendToList(response.data);

  if (response.pagination.has_more) {
    page++;
  } else {
    hideLoadMoreButton();
  }

  isLoading = false;
}

window.addEventListener('scroll', () => {
  if (isNearBottom()) {
    loadMore();
  }
});
```

### Pagination Controls

```javascript theme={null}
function PaginationControls({ pagination, onPageChange }) {
  const { page, pages, has_more } = pagination;

  return (
    <div>
      <button
        disabled={page === 1}
        onClick={() => onPageChange(page - 1)}
      >
        Previous
      </button>

      <span>Page {page} of {pages}</span>

      <button
        disabled={!has_more}
        onClick={() => onPageChange(page + 1)}
      >
        Next
      </button>
    </div>
  );
}
```
