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
| Signal | Endpoint | Field | Suggested action |
|---|---|---|---|
| Process up | /health | HTTP 200 | Alert on a failure |
| A chain not healthy | /status | chains[].status | Alert if not ok for several checks |
| No head movement | /status | chains[].head_age_seconds | Alert when above a threshold of your choice |
| Rows stopped | /status | chains[].indexed_since_start | Note: resets on restart |
| A source failing | /status | sources[].ok, error | Log it, since a fallback may cover it |
| Service restarted | /status | uptime_seconds falling | Log it. Expect a gap in rows. |
| Your rule failing | /rules | consecutive_failures, enabled | Alert above a few |
| Log cursor behind | /status | chains[].scan_lag_blocks | Alert if it keeps growing. Above 2,000, blocks are skipped. |
| Solana watch list failing | /status | watch_list.failures, last_ok | Log it |
| Keyed RPC budget spent | /status | keyed_rpc_daily_limit_reached | Log 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 -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
degradedread can clear on the next poll. The samples wait for three. - **Restarts show
startingfor a short time.** Do not alert on one read. - **
head_age_secondsgrows between polls.** Solana and the 30 second chains normally read above 30. - **
sources[].okreflects the last call.** A failing fallback does not matter while the primary works. - **
okdoes not mean complete.** Solana readsokwhile sampling. On indexed EVM chains, readscan_lag_blocksas well. See indexing.
Poll about once per minute. The state changes no faster than the polls behind it.