Developer integration
A developer who needs a large-transfer feed or token checks in an application can call the REST API directly, with no SDK. Official SDK packages are Planned, and they do not exist today. The OpenAPI file can generate a client.
#Workflow
- Prototype with no key. The limit is 30 units per minute.
- Sign in to the console with a Solana wallet, create a key, and keep it on the server.
POST /keysneeds that session, so a script cannot create keys without it. Browser code can use the API with no key, because CORS is open, and a key placed there is public. - Read
x-ratelimit-remainingand back off on 429. - Use
uidas your primary key, and store rows in your own database if you need more than 7 days. - Combine the live stream with REST polling, because the stream has no replay.
- For server-side events, use webhooks instead of a held-open stream.
#Page through transfers
before takes the meta.next_cursor of the last page. The value is null on the last page. Rows come newest first.
curl -s "https://farbeamlabs.com/api/v1/transfers?min_usd=1000000&limit=100" -H "Authorization: Bearer $FARBEAM_KEY" | jq '.meta.next_cursor'
curl -s "https://farbeamlabs.com/api/v1/transfers?min_usd=1000000&limit=100&before=1217" -H "Authorization: Bearer $FARBEAM_KEY"const BASE = "https://farbeamlabs.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.FARBEAM_KEY}` };
export async function* transfers(params = {}) {
let before = null;
for (;;) {
const qs = new URLSearchParams({ limit: "100", ...params, ...(before ? { before } : {}) });
const res = await fetch(`${BASE}/transfers?${qs}`, { headers });
if (res.status === 429) {
await new Promise((r) => setTimeout(r, Number(res.headers.get("retry-after") || 5) * 1000));
continue;
}
const { data, meta, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
yield* data;
if (!meta.next_cursor) return;
before = meta.next_cursor;
}
}import os, time, requests
BASE = "https://farbeamlabs.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['FARBEAM_KEY']}"}
def transfers(**params):
before = None
while True:
q = {"limit": 100, **params, **({"before": before} if before else {})}
res = requests.get(f"{BASE}/transfers", params=q, 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"]
if not body["meta"]["next_cursor"]:
return
before = body["meta"]["next_cursor"]#Live stream with gap filling
The stream sends transfer and block events. It has no replay. After a reconnect, call REST and stop when you reach a uid you already hold. A stream is limited to 6 per network, and the server sends a comment line every 25 seconds.
const seen = new Set();
const stream = new EventSource("https://farbeamlabs.com/api/live");
const handle = (ev) => { if (!seen.has(ev.uid)) { seen.add(ev.uid); process(ev); } };
stream.addEventListener("transfer", (m) => handle(JSON.parse(m.data)));
stream.addEventListener("open", async () => {
const res = await fetch("https://farbeamlabs.com/api/v1/transfers?limit=100");
const { data } = await res.json();
data.reverse().forEach(handle);
});import json, requests
seen = set()
def handle(ev):
if ev["uid"] not in seen:
seen.add(ev["uid"])
print(ev["chain"], ev["token"], ev["usd"])
while True:
for ev in reversed(requests.get("https://farbeamlabs.com/api/v1/transfers?limit=100", timeout=30).json()["data"]):
handle(ev)
try:
with requests.get("https://farbeamlabs.com/api/live", stream=True, timeout=60) as r:
event = None
for line in r.iter_lines(decode_unicode=True):
if line.startswith("event: "):
event = line[7:]
elif line.startswith("data: ") and event == "transfer":
handle(json.loads(line[6:]))
except requests.RequestException:
passdata.ts differs by route: an ISO string in REST and Unix milliseconds in the stream. Parse both if you merge them.
#Validate a rule in CI
POST /rules/validate needs no key and costs 1 unit. A bad expression returns 422 with a position. You can test a rule file during a build.
curl -s -X POST "https://farbeamlabs.com/api/v1/rules/validate" -H "content-type: application/json" -d '{"expression":"chain == \"base\" and usd >= 250k"}' | jq '.data | {valid, tested, matched}'Real result at 2026-11-04T19:21:20Z:
{ "valid": true, "tested": 1948, "matched": 487 }#Handling failure
| Response | Meaning | Action |
|---|---|---|
429 rate_limited | Units used up in this minute | Wait Retry-After, in seconds |
502 upstream_unavailable | A public source failed, and source names it | Retry with backoff. It usually clears soon. Cache the last good value |
Report with sources[].ok: false | One source failed | Keep the result, and treat unknown as unknown |
Wallet with partial: true | One chain failed | Show that chain as missing, not as zero |
429 daily_limit | 20,000 units used in this UTC day | Wait for the next UTC day |
401 invalid_key | Key revoked or wrong | Create a new key. Lost keys cannot be recovered |
Report or wallet with stale: true | Every source failed, and this is the last saved copy | Show its as_of time, or treat it as missing |
Record the x-request-id header in your logs. Include it when you report a problem.
#What this will not do
- No SLA, so do not put it in a critical path without a fallback.
- No GraphQL, no WebSocket and no bulk endpoint. One token report per call.
- No write operations and no raw RPC. See the RPC comparison.
- Field names can change during early access. Changes are recorded on the API changelog. There is no version promise beyond
/api/v1.