Farbeam
Docs / Get started

Pagination

Only List large transfers has cursor pagination. It returns rows newest first, by row id.

PartMeaning
limitRows per page, from 1 to 100. Default 25.
beforeReturn rows with an id lower than this value.
meta.next_cursorThe id of the last row on this page. Pass it as before. It is null on the last page.

Keep all your filters the same on each page. The cursor carries no filter state.

#How it behaves

  • The cursor is the integer row id. It is stable, and a page never shifts when new rows arrive, because new rows always get higher ids.
  • New rows that arrive during a walk do not appear in later pages. To catch them, start again from the top and stop at the first id you already hold.
  • Rows older than 7 days are deleted once per hour. An old cursor stays valid, and it finds fewer rows as old ones expire.
  • Row ids follow insert order, not block order. On EVM chains a poll can insert an older block's log after a newer block's native transfer, so ts is not strictly falling across a page. Sort by ts or block yourself if you need chain order.

Each page costs 1 unit. With no key, a limit of 30 units per minute caps a walk at 3,000 rows per minute. With a key, it caps at 12,000.

#Full loop

These loops read all transfers on Base of at least $1,000,000. They stop when next_cursor is null.

#!/usr/bin/env bash
# Needs curl and jq. Prints one JSON row per line.
base="https://farbeamlabs.com/api/v1/transfers?chain=base&min_usd=1000000&limit=100"
cursor=""
while :; do
  url="$base"
  [ -n "$cursor" ] && url="$base&before=$cursor"
  page=$(curl -s -H "Authorization: Bearer $FARBEAM_KEY" "$url")
  echo "$page" | jq -c '.data[]'
  cursor=$(echo "$page" | jq -r '.meta.next_cursor // empty')
  [ -z "$cursor" ] && break
  sleep 1
done
const BASE = "https://farbeamlabs.com/api/v1/transfers";

async function* walk(params) {
  let before = null;
  while (true) {
    const query = new URLSearchParams({ ...params, limit: "100" });
    if (before) query.set("before", String(before));
    const res = await fetch(`${BASE}?${query}`, {
      headers: { Authorization: `Bearer ${process.env.FARBEAM_KEY}` },
    });
    if (res.status === 429) {
      const wait = Number(res.headers.get("retry-after") || 5);
      await new Promise((resolve) => setTimeout(resolve, wait * 1000));
      continue;
    }
    const { data, meta, error } = await res.json();
    if (error) throw new Error(`${error.code}: ${error.message}`);
    yield* data;
    if (meta.next_cursor === null) return;
    before = meta.next_cursor;
  }
}

let total = 0;
for await (const row of walk({ chain: "base", min_usd: "1000000" })) {
  total += row.usd;
}
console.log(total);
import os, time
import requests

BASE = "https://farbeamlabs.com/api/v1/transfers"
HEADERS = {"Authorization": f"Bearer {os.environ['FARBEAM_KEY']}"}

def walk(**params):
    before = None
    while True:
        query = {**params, "limit": 100}
        if before:
            query["before"] = before
        res = requests.get(BASE, params=query, headers=HEADERS, timeout=30)
        if res.status_code == 429:
            time.sleep(int(res.headers.get("retry-after", 5)))
            continue
        body = res.json()
        if "error" in body:
            raise RuntimeError(body["error"]["code"])
        yield from body["data"]
        before = body["meta"]["next_cursor"]
        if before is None:
            return

print(sum(row["usd"] for row in walk(chain="base", min_usd=1000000)))

#Catching up from a known point

To fetch only rows newer than a row you hold, walk from the top and stop at your last id. This is the method to fill a gap after a stream drops. See the streaming guide.

JavaScript
async function newerThan(lastId) {
  const rows = [];
  let before = null;
  while (true) {
    const query = new URLSearchParams({ limit: "100" });
    if (before) query.set("before", String(before));
    const res = await fetch(`https://farbeamlabs.com/api/v1/transfers?${query}`);
    const { data, meta } = await res.json();
    for (const row of data) {
      if (row.id <= lastId) return rows;
      rows.push(row);
    }
    if (meta.next_cursor === null) return rows;
    before = meta.next_cursor;
  }
}

#Endpoints without a cursor

EndpointHow to limit it
/chains/{chain}/blockslimit from 1 to 25. The list is kept in memory and holds 25 blocks at most.
/rules/{id}/deliverieslimit from 1 to 100. Newest first, and no older pages.
/transfers/statshours from 1 to 168. Returns aggregates.
/status/historyhours from 1 to 720. Returns every sample in the window.
/labelsq, type and chain filters. Returns the whole match set.

Rows beyond the 7 day window are not available through any endpoint. A historical archive and data exports are Planned.