# Pagination

> Walk through lists of payments, refunds and events with cursors.

Source: https://rais.app/docs/pagination

Every endpoint that returns a collection, `GET /payments`, `GET /refunds` and `GET /events`, uses the same cursor-based pagination. Results come **newest first**.

## The list envelope

~~~json
{
  "object": "list",
  "data": [
    { "id": "5b1c2f4e-9d3a-4c61-8a7e-0f2d6b9e1a33", "object": "payment", "status": "paid" }
  ],
  "has_more": true,
  "next_cursor": "5b1c2f4e-9d3a-4c61-8a7e-0f2d6b9e1a33"
}
~~~


- `object` (string): always `list`.
- `data` (array): the objects of this page.
- `has_more` (boolean): `true` when older objects remain.
- `next_cursor` (string, nullable): pass it as `starting_after` to get the next page. `null` on the last page.


## Parameters


- `limit` (integer, optional): how many objects to return, from 1 to 100. Defaults to 20.
- `starting_after` (string, optional): the cursor of the page to return, which is the `next_cursor` of the previous page (the id of its last object).


A cursor that is not the id of an object of your store answers `400` with `invalid_cursor`. A cursor that is not a valid id does too. A `limit` below 1 or above 100 answers `400` with `parameter_out_of_range`.

## Walk through every page


**Request**

```bash
curl 'https://api.rais.app/v1/payments?limit=2' \
  -H "Authorization: Bearer rais_sk_test_..."
```


Send the first request without a cursor. While `has_more` is `true`, send the next one with `starting_after` set to `next_cursor`.

~~~javascript
async function* allPayments(query = "") {
  let cursor = null;
  do {
    const url = new URL("https://api.rais.app/v1/payments");
    url.searchParams.set("limit", "100");
    if (cursor) url.searchParams.set("starting_after", cursor);
    for (const [key, value] of new URLSearchParams(query)) url.searchParams.set(key, value);

    const response = await fetch(url, { headers: { Authorization: `Bearer ${process.env.RAIS_SECRET_KEY}` } });
    const page = await response.json();
    yield* page.data;
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);
}

for await (const payment of allPayments("status=paid")) console.log(payment.id);
~~~

~~~python
import os
import requests

def all_payments(**filters):
    cursor = None
    while True:
        params = {"limit": 100, **filters}
        if cursor:
            params["starting_after"] = cursor
        page = requests.get(
            "https://api.rais.app/v1/payments",
            params=params,
            headers={"Authorization": f"Bearer {os.environ['RAIS_SECRET_KEY']}"},
            timeout=30,
        ).json()
        yield from page["data"]
        if not page["has_more"]:
            return
        cursor = page["next_cursor"]

for payment in all_payments(status="paid"):
    print(payment["id"])
~~~

## Good to know

- Pages are stable: new objects appear at the start of the list and never shift the pages you are still walking through.
- Filters work with pagination. For payments: `status`, `reference`, `created_gte` and `created_lte` (Unix seconds). For refunds: `payment`. For events: `type`. A filter with an invalid value answers `400 invalid_parameter`, but a parameter the API does not know is **ignored**: `?stauts=paid` returns the unfiltered list.
- Events can be listed for 30 days only.
- Pass the cursor exactly as received. Do not build one yourself.
