# Pagination

List endpoints return results one page at a time. You choose the page and its size with query parameters, and the response tells you how many pages exist. The parameter names and the response shape differ between endpoints, so check the table below before you write a loop.

## Request

Every list takes a page number and `pageSize`. The page number's name depends on the endpoint: `pageNumber` or `pageOffset`. Both mean the page to return, starting at 1 (`pageOffset` is **not** a row offset). A parameter an endpoint doesn't read is ignored, so with the wrong name you always get page 1.

| Endpoint | Page parameter | Default `pageSize` | Maximum `pageSize` | Order | Items in |
|---|---|---|---|---|---|
| [Find transfers](api:listTransfers) | `pageNumber` | 10 | 100 | Newest first | `data` |
| [List banks](api:listBanks) | `pageNumber` | 10 | 100 | Oldest first | `data` |
| [List customers](api:listSubClients) | `pageOffset` | 10 | 500 | Newest first (`sortField`, `sortDirection`) | `data` |
| [List accounts](api:listAccounts) | `pageOffset` | 10 | none | Newest first | `data` |
| [List beneficiaries](api:listBeneficiaries) | `pageOffset` | 10 | none | **Oldest** first (`sortField`, `sortDirection`) | `data` |
| [List receivables](api:listReceivables) | `pageOffset` | 10 | 100 | Newest first | `data` |
| [List conversions](api:listFxTrades) | `pageOffset` | 10 | 100 | Newest first (`sortField`, `sortDirection`) | `items` |
| [Get account transactions](api:getTransactionHistory) | `pageOffset` | 10 | 1000 (above it, an empty list) | Newest first | `data` |
| [Get an account statement](api:getAccountStatement) | `pageOffset` or `pageNumber` | 100 | none | Newest first | `statement.data` |
| [List banks you can link](api:listLinkingBanks) | `pageNumber` | 20 | 50 | Newest first | `banks` |
| [Search linking requests](api:searchAccountLinking) | `pageNumber` | 10 | none | Newest first | `accounts` |
| [List letters of credit](api:listLettersOfCredit) | `pageNumber` | 10 | 100 | Depends on the filters (`sortField`, `sortDirection`) | `data` |
| [List export letters of credit](api:listExportLettersOfCredit) | `pageNumber` | 10 | 100 | Newest first | `data.data` (page fields inside `data` too) |
| [List import letters of credit](api:listImportLettersOfCredit) | `pageNumber` | 10 | 100 | Newest first | `data` |
| [List guarantees](api:listGuarantees) | `pageNumber` | 10 | 100 | Last updated first (`sortField`, `sortDirection`) | `data` |
| [List inward guarantees](api:listInwardGuarantees) | `pageNumber` | 10 | 100 | `sortField`, `sortDirection` | `data` |
| [List outward guarantees](api:listOutwardGuarantees) | `pageNumber` | 10 | 100 | Newest first | `data` |
| [List lifecycle alerts](api:listTradeFinanceLifecycleAlerts) | `pageNumber` | 10 | 100 | Newest first | `data` |

A `pageSize` above the maximum isn't rejected: you get the maximum. The exception is **Get account transactions**, which returns an empty list for a `pageSize` above 1000. Where there is no maximum, keep `pageSize` at 100 or less so responses stay fast.

```bash
curl "$MG_BASE_URL/client-service/api/v1/beneficiaries?pageOffset=2&pageSize=50" \
  -H "mp-api-key: $MG_API_KEY"
```

## Response

Most lists put the items in `data` and the paging details beside it:

```json
{
  "data": [
    { "entityId": "c0a8012e-…", "accountName": "Ada Okafor" }
  ],
  "totalItems": 137,
  "pageSize": 50,
  "currentPage": 2,
  "totalPages": 3
}
```

| Field | Meaning |
|---|---|
| `data` | The items on this page. |
| `totalItems` | Total items matching your filters, across all pages. |
| `pageSize` | Page size used. It can be lower than you asked for if you went above the maximum. |
| `currentPage` | The page returned. |
| `totalPages` | Pages available at this page size. `0` when nothing matches. |

The exceptions:

- [List conversions](api:listFxTrades) returns `count` (total items) and `items`, with no page fields.
- [List receivables](api:listReceivables) returns `data`, `count` (items on this page) and `totalCount` (all matching items), with no page fields.
- [List banks you can link](api:listLinkingBanks) returns the items in `banks`, and [Search linking requests](api:searchAccountLinking) in `accounts`, beside the usual page fields.
- [Get an account statement](api:getAccountStatement) nests the items and the page fields in `statement`: `statement.data`, `statement.page`, `statement.totalPages`.
- [List accounts](api:listAccounts) and [List beneficiaries](api:listBeneficiaries) answer `404` ("account not found", "beneficiary not found") instead of an empty page when nothing matches, including when you ask for a page past the last one.

## Reading every page

This function reads every page of any list in the table, whatever its shape. It uses `callMultigate` from the [error handler](02-errors.md#a-complete-error-handler).

```javascript
import { callMultigate, Outcome } from './multigate.js'; // the handler from the Errors page

const PAGE_SIZE = 100;

// path: for example '/client-service/api/v1/beneficiaries'
// pageParam: 'pageNumber' or 'pageOffset', from the table above
export async function listAll(path, pageParam, filters = {}) {
  const items = [];
  for (let page = 1; ; page += 1) {
    const url = new URL(`${process.env.MG_BASE_URL}${path}`);
    for (const [name, value] of Object.entries(filters)) url.searchParams.set(name, value);
    url.searchParams.set(pageParam, String(page));
    url.searchParams.set('pageSize', String(PAGE_SIZE));

    const result = await callMultigate(url.toString()); // sends MG_API_KEY
    if (result.status === 404 && isEmptyListError(result)) return items; // accounts and beneficiaries
    if (result.outcome !== Outcome.SUCCESS) {
      throw new Error(`Page ${page} failed with HTTP ${result.status}: ${result.errors[0]?.message}`);
    }

    const { pageItems, isLastPage } = readPage(result.body, page, items.length);
    items.push(...pageItems);
    if (isLastPage || pageItems.length === 0) return items;
  }
}

function readPage(body, page, itemsSoFar) {
  if (Array.isArray(body.items)) {
    // List conversions: { count, items }
    return { pageItems: body.items, isLastPage: itemsSoFar + body.items.length >= body.count };
  }
  if (typeof body.totalCount === 'number') {
    // List receivables: { count, totalCount, data }
    return { pageItems: body.data, isLastPage: itemsSoFar + body.data.length >= body.totalCount };
  }
  const container = body.statement ?? body; // Get an account statement nests everything in `statement`
  const pageItems = container.data ?? container.banks ?? container.accounts ?? [];
  return { pageItems, isLastPage: page >= container.totalPages };
}

function isEmptyListError(result) {
  return result.errors.some((e) => e.message === 'account not found' || e.message === 'beneficiary not found');
}
```

A `404` with "Resource not found" is not an empty list: it means your API user lacks the permission for that list.

:::warning[Deep pages are refused on the customer list]
[List customers](api:listSubClients) refuses a request that would skip more than **100,000** customers (for example `pageOffset=202` with `pageSize=500`) with HTTP `422`. Narrow the results with filters instead of paging that far.
:::

## Ordering and changing data

The **Order** column above gives each list's default order. If new items are created while you page through a list, an item can move to the next page and appear twice, or (on oldest-first lists) new items appear on the last page. To get a stable snapshot, filter by a date range that ends in the past, and de-duplicate by ID.
