Farbeam
Docs / Get started

Authentication

Most endpoints work without a key. A key raises your rate limit from 30 to 120 units per minute, and it gives access to alert rules. To create a key, sign in with a Solana wallet. There is no password and no email address.

#Sign in

The console does these steps for you with a browser wallet such as Phantom, Solflare or Backpack. Signing sends no transaction and costs nothing. EVM wallet sign-in is Planned.

  1. POST /auth/challenge with your Solana address. The reply holds a nonce and a message.
  2. Sign the exact message text with the wallet.
  3. POST /auth/verify with the address, nonce and signature. The reply sets a session cookie.

The three samples below show the same steps. The signature must come from your wallet, so each sample has a place for it.

ADDRESS="your-solana-address"
curl -s -X POST "https://farbeamlabs.com/api/v1/auth/challenge" \
  -H "content-type: application/json" \
  -d "{\"address\":\"$ADDRESS\"}" | jq -r '.data.nonce, .data.message'
# Sign the message with your wallet. Then send the nonce and the signature.
curl -s -c farbeam-cookies.txt -X POST "https://farbeamlabs.com/api/v1/auth/verify" \
  -H "content-type: application/json" \
  -d "{\"address\":\"$ADDRESS\",\"nonce\":\"$NONCE\",\"signature\":\"$SIGNATURE\"}"
# Create a key with the session cookie.
curl -s -b farbeam-cookies.txt -X POST "https://farbeamlabs.com/api/v1/keys" \
  -H "content-type: application/json" \
  -d '{"label":"my-app"}'
// Runs on a farbeamlabs.com page. The cookie is SameSite=Strict, so sign-in from another site does not work.
const call = async (path, body) => {
  const res = await fetch(`/api/v1${path}`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(body),
  });
  const { data, error } = await res.json();
  if (error) throw new Error(`${error.code}: ${error.message}`);
  return data;
};

const wallet = window.phantom.solana;
const { publicKey } = await wallet.connect();
const address = publicKey.toBase58();
const { nonce, message } = await call("/auth/challenge", { address });
const { signature: bytes } = await wallet.signMessage(new TextEncoder().encode(message), "utf8");
const signature = btoa(String.fromCharCode(...bytes));
await call("/auth/verify", { address, nonce, signature });
const key = await call("/keys", { label: "my-app" });
console.log(key.id, key.key);
import requests

API = "https://farbeamlabs.com/api/v1"
address = "your-solana-address"

def sign_with_wallet(message):
    # Return the base64 or base58 signature of the message from your wallet tool.
    print(message)
    return input("Signature: ").strip()

session = requests.Session()
challenge = session.post(f"{API}/auth/challenge", json={"address": address}, timeout=30).json()["data"]
signature = sign_with_wallet(challenge["message"])
session.post(
    f"{API}/auth/verify",
    json={"address": address, "nonce": challenge["nonce"], "signature": signature},
    timeout=30,
).raise_for_status()
key = session.post(f"{API}/keys", json={"label": "my-app"}, timeout=30).json()["data"]
print(key["id"], key["key"])
Sign-in factValue
Challenge life5 minutes, one use, right or wrong
Challenge limit30 per hour per network. Above it, 429 rate_limited.
Signature formatEd25519, 64 bytes, as base64 or base58
Session life7 days
Cookiefb_session, HttpOnly, SameSite=Strict, Path=/api, and Secure over HTTPS
Sign outPOST /auth/logout

The session works for requests that send no key. Those requests spend a hidden key of the account, named console, with the same limits as any other key. The session does not work from another origin, because the cookie is SameSite=Strict and the API sends no credentials header.

#Create a key

POST /keys needs a session. See Create an API key. The response has status 201. The values below are zeroed. The rest was captured on 2026-11-04 UTC.

JSON
{
  "data": {
    "id": "key_000000000000",
    "key": "fbk_000000000000000000000000000000000000000000000000",
    "label": "my-app",
    "requests_per_minute": 120,
    "note": "Save this key now. Farbeam stores only a hash, so it cannot show the key again."
  },
  "meta": {
    "request_id": "req_4e9febf6fa8f",
    "as_of": "2026-11-04T19:15:07.215Z"
  }
}
RuleValue
Key formatfbk_ plus 48 hex characters
Key id formatkey_ plus 12 hex characters. The id is not secret.
LabelOptional. Cut to 60 characters. Empty becomes unnamed.
Active keys5 per account. A sixth call returns 409 key_limit. Revoked keys do not count.
Rate-limit cost0 units
Daily limit20,000 units per key per UTC day. See rate limits.
StorageSHA-256 hash only. A lost key cannot be recovered. Create a new one.

#Send a key

Use either header. Authorization is the documented form.

HeaderExample
AuthorizationAuthorization: Bearer fbk_...
x-api-keyx-api-key: fbk_...
curl -s "https://farbeamlabs.com/api/v1/me" -H "Authorization: Bearer $FARBEAM_KEY"
const res = await fetch("https://farbeamlabs.com/api/v1/me", {
  headers: { Authorization: `Bearer ${process.env.FARBEAM_KEY}` },
});
console.log((await res.json()).data);
import os, requests

res = requests.get(
    "https://farbeamlabs.com/api/v1/me",
    headers={"Authorization": f"Bearer {os.environ['FARBEAM_KEY']}"},
    timeout=30,
)
print(res.json()["data"])

Do not place a key in a URL. The API reads keys from headers only.

#Which endpoints need a key

AccessEndpoints
No keyAll read endpoints for chains, gas, prices, transfers, wallets, tokens, transactions, labels, markets and status. Also Validate a rule, the /auth endpoints.
SessionAccount, Create an API key, Revoke a key, Link Telegram, Unlink Telegram. A key is not accepted for these.
Key or sessionCurrent key, Revoke the current key, and every /rules endpoint except /rules/validate.

The live stream at /api/live reads no key.

#Failure modes

HTTPCodeWhen
401missing_keyThe endpoint requires a key and the request has none.
401invalid_keyA key was sent, and it is unknown or revoked.
401sign_in_requiredThe endpoint needs a session and the request has none.
401invalid_challengeThe nonce expired, was used, or belongs to another address. Start again.
401invalid_signatureThe signature does not match the wallet and message. The nonce is now used. Start again.

A bad key fails on every endpoint, including public ones. The API does not fall back to anonymous access, so a revoked key in a config file returns 401 on /prices too.

JSON
{
  "error": {
    "code": "invalid_key",
    "message": "The API key is unknown or revoked.",
    "request_id": "req_295bd7a14a3e"
  }
}

#Read and revoke a key

GET /me returns the key id, label, creation time, last use, limits, rule count and up to 30 days of daily usage. The usage[].calls value adds the unit cost of each accepted request, and adds 1 for a request whose cost is 0.

There are two ways to revoke a key, and they treat rules differently.

CallAuthEffect on rules
DELETE /keys/{id}SessionRules stay on and stay with the account
DELETE /meThe key itselfTurns off the rules that this key created

Neither can be undone. There is no rotate endpoint. To rotate, sign in, create a new key, point your code at it, then revoke the old key with DELETE /keys/{id}. If you lose a key, do the same. Your rules keep running.

GET /rules with any key of an account lists all rules of that account. Read security for storage and handling advice.