Farbeam
Docs / SDK guides

Recipe: status monitoring

If your service depends on Farbeam data, monitor it yourself. There is no SLA and no status email. An SLA is Planned. GET /status costs 0 units, so you can poll it freely within reason. The status page shows the same data for people.

#What to watch

SignalEndpointFieldSuggested action
Process up/healthHTTP 200Alert on a failure
A chain not healthy/statuschains[].statusAlert if not ok for several checks
No head movement/statuschains[].head_age_secondsAlert when above a threshold of your choice
Rows stopped/statuschains[].indexed_since_startNote: resets on restart
A source failing/statussources[].ok, errorLog it, since a fallback may cover it
Service restarted/statusuptime_seconds fallingLog it. Expect a gap in rows.
Your rule failing/rulesconsecutive_failures, enabledAlert above a few
Log cursor behind/statuschains[].scan_lag_blocksAlert if it keeps growing. Above 2,000, blocks are skipped.
Solana watch list failing/statuswatch_list.failures, last_okLog it
Keyed RPC budget spent/statuskeyed_rpc_daily_limit_reachedLog it. Lookups use the public endpoint.

Read chain status for what each state means. All chains use the same thresholds: degraded at 90 seconds of lag or 2 failures in a row, and down after 180 seconds with no good poll.

/health returns status: "ok" whenever the process can answer. A check on that alone misses a stalled poller. Read chains_ok too.

#Code

#!/usr/bin/env bash
# Exits 1 if any chain is not ok. Use it from cron or a health check.
bad=$(curl -sf --max-time 10 "https://farbeamlabs.com/api/v1/status" \
  | jq -r '.data.chains[] | select(.status != "ok") | "\(.chain) \(.status) \(.last_error // "")"') || { echo "status endpoint failed"; exit 1; }
if [ -n "$bad" ]; then echo "$bad"; exit 1; fi
echo ok
// monitor.mjs: run with node monitor.mjs
const STATUS = "https://farbeamlabs.com/api/v1/status";
const INTERVAL_MS = 60000;
const ALERT_AFTER = 3; // bad checks in a row
const strikes = new Map();
let lastUptime = null;

async function alert(message) {
  // Replace this with a call to your own pager or chat.
  console.error("ALERT", new Date().toISOString(), message);
}

async function check() {
  const res = await fetch(STATUS, { signal: AbortSignal.timeout(10000) });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const { data } = await res.json();
  if (lastUptime !== null && data.uptime_seconds < lastUptime) await alert("service restarted, expect a gap in rows");
  lastUptime = data.uptime_seconds;
  for (const chain of data.chains) {
    const count = chain.status === "ok" ? 0 : (strikes.get(chain.chain) ?? 0) + 1;
    strikes.set(chain.chain, count);
    if (count === ALERT_AFTER) await alert(`${chain.chain} is ${chain.status}: ${chain.last_error ?? "no error text"}`);
  }
  for (const source of data.sources.filter((s) => s.ok === false)) {
    console.log("source failing", source.name, source.error);
  }
}

setInterval(() => check().catch((err) => alert(`status check failed: ${err.message}`)), INTERVAL_MS);
await check();
# monitor.py: run with python3 monitor.py
import time
from datetime import datetime, timezone
import requests

STATUS = "https://farbeamlabs.com/api/v1/status"
ALERT_AFTER = 3  # bad checks in a row
strikes, last_uptime = {}, None

def alert(message):
    # Replace this with a call to your own pager or chat.
    print("ALERT", datetime.now(timezone.utc).isoformat(), message)

while True:
    try:
        res = requests.get(STATUS, timeout=10)
        res.raise_for_status()
        data = res.json()["data"]
        if last_uptime is not None and data["uptime_seconds"] < last_uptime:
            alert("service restarted, expect a gap in rows")
        last_uptime = data["uptime_seconds"]
        for chain in data["chains"]:
            count = 0 if chain["status"] == "ok" else strikes.get(chain["chain"], 0) + 1
            strikes[chain["chain"]] = count
            if count == ALERT_AFTER:
                alert(f"{chain['chain']} is {chain['status']}: {chain['last_error']}")
        for source in data["sources"]:
            if source["ok"] is False:
                print("source failing", source["name"], source["error"])
    except (requests.RequestException, ValueError) as err:
        alert(f"status check failed: {err}")
    time.sleep(60)

#Monitor your rules

Farbeam sends no notice when a rule turns off. Add this to the same job. GET /rules costs 1 unit.

curl -s "https://farbeamlabs.com/api/v1/rules" -H "Authorization: Bearer $FARBEAM_KEY" \
  | jq -r '.data[] | select(.enabled == false or .consecutive_failures >= 3) | [.id, .name, .enabled, .consecutive_failures] | @tsv'
const res = await fetch("https://farbeamlabs.com/api/v1/rules", {
  headers: { Authorization: `Bearer ${process.env.FARBEAM_KEY}` },
});
const { data } = await res.json();
for (const rule of data) {
  if (!rule.enabled || rule.consecutive_failures >= 3) console.log(rule.id, rule.name, rule.enabled, rule.consecutive_failures);
}
import os, requests

res = requests.get(
    "https://farbeamlabs.com/api/v1/rules",
    headers={"Authorization": f"Bearer {os.environ['FARBEAM_KEY']}"},
    timeout=30,
)
for rule in res.json()["data"]:
    if not rule["enabled"] or rule["consecutive_failures"] >= 3:
        print(rule["id"], rule["name"], rule["enabled"], rule["consecutive_failures"])

#Reading history

Status history returns one sample per chain per 5 minutes, up to 30 days back. Use it to add context to an alert, or to count bad samples in your own report. The record began when the service began measuring, so read measuring_since from /status first.

curl
curl -s "https://farbeamlabs.com/api/v1/status/history?hours=6" \
  | jq '.data.chains.ethereum | map(select(.state != "ok")) | length'

#Avoid false alarms

  • Allow a few checks. One degraded read can clear on the next poll. The samples wait for three.
  • **Restarts show starting for a short time.** Do not alert on one read.
  • **head_age_seconds grows between polls.** Solana and the 30 second chains normally read above 30.
  • **sources[].ok reflects the last call.** A failing fallback does not matter while the primary works.
  • **ok does not mean complete.** Solana reads ok while sampling. On indexed EVM chains, read scan_lag_blocks as well. See indexing.

Poll about once per minute. The state changes no faster than the polls behind it.