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

# Pagination

> Cursor-based paging over every list endpoint.

Cursor-paginated list endpoints return a `{ data, meta }` envelope. There are no
page numbers and no offsets anywhere in the API.

| Cursor-paginated              | Bare array                             |
| ----------------------------- | -------------------------------------- |
| `GET /v1/payouts`             | `GET /v1/banks`                        |
| `GET /v1/inflows`             | `GET /v1/refund-destinations`          |
| `GET /v1/refunds`             | `GET /v1/payouts/{payout_id}/refunds`  |
| `GET /v1/webhooks/deliveries` | `GET /v1/payouts/{payout_id}/outflows` |

The right-hand column returns naturally bounded lists — every bank in a corridor,
every attempt against one payout — so they are returned whole rather than paged.

```bash theme={null}
curl "$SPENDIN_BASE_URL/v1/payouts?limit=20" \
  -H "X-API-Key: $SPENDIN_API_KEY"
```

```json theme={null}
{
  "data": [],
  "meta": {
    "next_cursor": "eyJpZCI6IjEyMyJ9",
    "has_more": true,
    "limit": 20
  }
}
```

`data` holds this page's records — payouts, inflows, refunds, or deliveries
depending on the endpoint. `meta` is identical in shape everywhere.

## Walking the pages

Pass `next_cursor` back as `cursor` to get the following page. Stop when
`has_more` is `false`.

```typescript theme={null}
async function listAllPayouts(): Promise<Payout[]> {
  const all: Payout[] = [];
  let cursor: string | null = null;

  do {
    const url = new URL(`${base_url}/v1/payouts`);
    url.searchParams.set('limit', '100');
    if (cursor) url.searchParams.set('cursor', cursor);

    const response = await fetch(url, { headers: { 'X-API-Key': api_key } });
    const page = await response.json();

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

  return all;
}
```

Branch on `has_more`, not on `next_cursor` being non-null, and not on a short
page — a full page can still be the last one.

## Parameters

| Parameter | Type    | Description                                           |
| --------- | ------- | ----------------------------------------------------- |
| `limit`   | integer | Results per page, 1–100. Defaults to 20.              |
| `cursor`  | string  | Opaque cursor from the previous page's `next_cursor`. |

Results are ordered newest first, by creation time. The cursor is an opaque
base64 token — treat it as a string, do not decode it or construct one, and do
not persist it beyond the walk you are doing.

<Note>
  Because the order is newest first, records created while you are paging appear on
  page one and will not shift results you have already read. For a stable snapshot
  of a busy account, page quickly and reconcile by payout ID rather than by
  position.
</Note>

## Filtering

`GET /v1/refunds` accepts a `status` filter (`PENDING`, `PROCESSING`, `SUCCESSFUL`,
`FAILED`) alongside the pagination parameters. It composes with the cursor, so a
filtered walk pages the same way an unfiltered one does.

```bash theme={null}
curl "$SPENDIN_BASE_URL/v1/refunds?status=FAILED&limit=50" \
  -H "X-API-Key: $SPENDIN_API_KEY"
```
