Farbeam
Docs / Get started

Rate limits

Farbeam limits requests in units per minute. Each endpoint has a cost in units.

CallerLimitBucket
No key30 units per minuteOne bucket per network, keyed by a SHA-256 hash of the client IP
Free key120 units per minute, and 20,000 units per UTC dayOne bucket per key

#Unit costs

EndpointCost
GET /wallets/{address}, GET /wallets/{address}/activity, GET /tokens/{chain}/{address}3
GET /tx/{chain}/{hash}2
GET /health, GET /status, GET /me, DELETE /me0
GET /auth/session, POST /auth/logout, GET /account0
POST /keys, DELETE /keys/{id}, POST /account/telegram, DELETE /account/telegram0
Every other endpoint1

The live stream has no unit cost. It has its own limit of 6 open streams per network and 400 in total.

#How the window works

Windows are fixed. Each one starts on a whole UTC minute and resets to zero on the next one. A client that spends its limit in the last second of one minute can spend it again in the first second of the next.

The counter lives in process memory, so it also resets when the service restarts. Three details matter when you write a client:

  • The cost is added before the check. A request that goes over still adds to the counter for that window.
  • A request that fails validation, such as a bad address, still costs its units. It has already passed the rate check.
  • A request for an unknown path, a wrong method or a bad key costs nothing and returns no rate headers.

#Headers

Every response from a metered endpoint carries three headers. Endpoints with a cost of 0 do not send them.

HeaderMeaningExample
x-ratelimit-limitUnits allowed per minute for this caller120
x-ratelimit-remainingUnits left in this window. Never below 0.118
x-ratelimit-resetUnix time in seconds when the next window starts1793820240
Retry-AfterSeconds to wait. Sent with a unit-limit 429.56

All of them are readable from browser code. The API sets access-control-expose-headers.

#A real 429

This response was captured on 2026-11-04 UTC by spending a test key's limit.

HTTP
HTTP/1.1 429 Too Many Requests
x-ratelimit-limit: 120
x-ratelimit-remaining: 0
x-ratelimit-reset: 1793820240
retry-after: 56
JSON
{
  "error": {
    "code": "rate_limited",
    "message": "Limit is 120 units per minute.",
    "retry_after": 12,
    "request_id": "req_53a105fabbcd"
  }
}

The body repeats the wait in retry_after, in seconds.

Two other caps also return 429 rate_limited, and neither sends Retry-After. They are the sign-in challenge cap (30 per hour per network) and the test delivery cap (10 per rule per hour). Waiting one minute does not clear them, so do not retry them in a loop.

#Daily limit

Each key can spend 20,000 units per UTC day. Above that, requests to endpoints with a cost return 429 daily_limit until 00:00 UTC, with no Retry-After. Requests without a key have no daily limit. Endpoints with a cost of 0 are not blocked. usage[].calls from Current key shows the count, and it adds 1 for a request whose cost is 0.

#Backoff code

Wait for Retry-After when it is present. For 502 and network errors, use a capped exponential delay with jitter. Do not retry other 4xx codes.

# Retries a GET up to 5 times. Prints the body on success.
url="https://farbeamlabs.com/api/v1/transfers?limit=5"
for attempt in 1 2 3 4 5; do
  code=$(curl -s -o /tmp/farbeam.json -D /tmp/farbeam.hdr -w "%{http_code}" "$url")
  if [ "$code" = "200" ]; then cat /tmp/farbeam.json; exit 0; fi
  if [ "$code" != "429" ] && [ "$code" != "502" ] && [ "$code" != "000" ]; then cat /tmp/farbeam.json; exit 1; fi
  wait=$(grep -i '^retry-after:' /tmp/farbeam.hdr | tr -dc '0-9')
  sleep "${wait:-$((2 ** attempt))}"
done
exit 1
export async function getWithBackoff(url, init = {}, tries = 5) {
  let lastError;
  for (let attempt = 0; attempt < tries; attempt++) {
    let wait;
    try {
      const res = await fetch(url, init);
      if (res.status !== 429 && res.status !== 502) return res;
      const after = Number(res.headers.get("retry-after"));
      lastError = new Error(`HTTP ${res.status}`);
      wait = after > 0 ? after * 1000 : null;
    } catch (err) {
      lastError = err;
    }
    wait ??= Math.min(30000, 1000 * 2 ** attempt) * (0.5 + Math.random() / 2);
    await new Promise((resolve) => setTimeout(resolve, wait));
  }
  throw lastError;
}
import random, time
import requests

def get_with_backoff(url, tries=5, **kwargs):
    last_error = None
    for attempt in range(tries):
        wait = None
        try:
            res = requests.get(url, timeout=30, **kwargs)
            if res.status_code not in (429, 502):
                return res
            last_error = RuntimeError(f"HTTP {res.status_code}")
            after = res.headers.get("retry-after")
            wait = int(after) if after else None
        except requests.RequestException as err:
            last_error = err
        if wait is None:
            wait = min(30, 2 ** attempt) * (0.5 + random.random() / 2)
        time.sleep(wait)
    raise last_error

#Spend units well

  • Cache token reports and wallet reads on your side. The service caches them too, for the times listed here, so polling faster than that returns the same data and still costs units.
  • Use /transfers with min_usd and chain filters rather than filtering pages on the client.
  • Use the live stream or a webhook rather than polling /transfers.
  • Check /status, which costs 0 units, before a batch job.

There is no paid tier and no way to buy a higher limit today. Paid plans are Planned. See limits for every limit in one table.