Data API

Pagination

Every list endpoint answers with a meta block. Read it rather than counting.

meta
{
  "limit": 2,
  "offset": 0,
  "sort": "skill",
  "tier": "enterprise",
  "next_cursor": "MS45Nzk1MzI2NzM5NzEzMjY4fDB4YjkwMTJlMGQ5YjYw...",
  "has_more": true,
  "note": "quality_score is a historical activity signal, not a return forecast."
}

Parameters

NameTypeDescription
limitintRows per page. Ask for as many as you can use — a page of 100 costs the same one credit as a page of 10.
offsetintSkip N rows. Fine for the first few pages; see the warning below.
cursorstringOpaque token from meta.next_cursor. The correct way to walk a whole list.

meta fields

NameTypeDescription
has_moreboolWhether another page exists. This — not the row count — is how you know when to stop.
next_cursorstring | nullPass back as ?cursor= for the next page.
sortstringThe ordering actually applied, which may not be the one you asked for.
tierstringThe plan this response was served under.
notestring?Occasional caveat about the data itself. Worth logging.
Use the cursor for anything deep. Offset paging asks the database to count past every skipped row, so it slows down the further you go — and if the underlying ranking shifts between requests, rows can repeat or be missed. A cursor is anchored to a position in the result set and has neither problem.

Walking a full list

JavaScript
async function* everyWallet(pageSize = 100) {
  let cursor = null;
  do {
    const qs = new URLSearchParams({ limit: String(pageSize) });
    if (cursor) qs.set('cursor', cursor);

    const res = await fetch(`${BASE}/v1/wallets?${qs}`, {
      headers: { 'X-API-Key': KEY },
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);

    const { wallets, meta } = await res.json();
    yield* wallets;
    // has_more is the stop condition. A short page is not the end of the list.
    cursor = meta.has_more ? meta.next_cursor : null;
  } while (cursor);
}

for await (const w of everyWallet()) {
  console.log(w.address, w.skill_score);
}
Python
def every_wallet(page_size=100):
    cursor = None
    while True:
        params = {"limit": page_size}
        if cursor:
            params["cursor"] = cursor
        r = requests.get(f"{BASE}/v1/wallets", params=params,
                         headers={"X-API-Key": KEY}, timeout=30)
        r.raise_for_status()
        body = r.json()
        yield from body["wallets"]
        meta = body["meta"]
        if not meta.get("has_more"):
            return
        cursor = meta["next_cursor"]

Cursors are opaque and short-lived — treat them as a token to hand straight back, never as something to store, parse or construct.