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.

View as MarkdownOpenAPI specAll docs for LLMs

How it works

GET /v1/apps, GET /v1/apps/{slug}/companies and GET /v1/events 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:

"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 runs oldest first. Save the last cursor and resume from it to read only new events next time.

Read a whole list

JavaScript:

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:

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, free) and stop once you have the rows you need.