# Pagination

List endpoints return up to **500** rows a page (default **100**) and a `pagination.nextCursor` you pass back to get the next page, until it is `null`.

## How it works

[`GET /v1/apps`](https://installgraph.com/docs/reference/list-apps.md), [`GET /v1/apps/{slug}/companies`](https://installgraph.com/docs/reference/list-app-companies.md) and [`GET /v1/events`](https://installgraph.com/docs/reference/list-events.md) take:

| Parameter | Meaning |
| --- | --- |
| `limit` | Rows per page, 1–500, default 100. On billed lists it is also your spend cap per page. |
| `cursor` | `pagination.nextCursor` from the previous page, unchanged. |

and return:

```json
"pagination": { "nextCursor": "eyJrIjoi…", "hasMore": true }
```

- Send the **same filters** with every page. A cursor is a position, not an offset: rows added while you page do not shift or repeat earlier rows.
- A page can hold **fewer rows than `limit` while `hasMore` is true** (filters applied after the scan, or a balance that covered only part of the page). Keep going until `nextCursor` is `null`.
- Cursors are opaque. Do not build or edit one; an edited cursor gets `400 invalid_cursor`.
- [`GET /v1/events`](https://installgraph.com/docs/reference/list-events.md) runs oldest first. Save the last cursor and resume from it to read only new events next time.

## Read a whole list

JavaScript:

```js
async function* pages(path, params) {
  let cursor;
  do {
    const qs = new URLSearchParams({ ...params, ...(cursor ? { cursor } : {}) });
    const res = await fetch(`https://api.installgraph.com${path}?${qs}`, {
      headers: { Authorization: `Bearer ${process.env.INSTALLGRAPH_API_KEY}` },
    });
    const body = await res.json();
    if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
    yield body.data;
    cursor = body.pagination.nextCursor;
  } while (cursor);
}

for await (const rows of pages("/v1/apps", { category: "reviews", limit: "500" })) {
  for (const app of rows) console.log(app.slug);
}
```

Python:

```python
import os, requests

def pages(path, params):
    headers = {"Authorization": f"Bearer {os.environ['INSTALLGRAPH_API_KEY']}"}
    cursor = None
    while True:
        query = {**params, **({"cursor": cursor} if cursor else {})}
        body = requests.get(f"https://api.installgraph.com{path}", params=query, headers=headers).json()
        if "error" in body:
            raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
        yield body["data"]
        cursor = body["pagination"]["nextCursor"]
        if not cursor:
            break

for rows in pages("/v1/apps", {"category": "reviews", "limit": 500}):
    for app in rows:
        print(app["slug"])
```

On a billed list, count first ([`GET /v1/apps/{slug}/companies/count`](https://installgraph.com/docs/reference/count-app-companies.md), free) and stop once you have the rows you need.
