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.
POST /auth/challengewith your Solana address. The reply holds anonceand amessage.- Sign the exact
messagetext with the wallet. POST /auth/verifywith 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 fact | Value |
|---|---|
| Challenge life | 5 minutes, one use, right or wrong |
| Challenge limit | 30 per hour per network. Above it, 429 rate_limited. |
| Signature format | Ed25519, 64 bytes, as base64 or base58 |
| Session life | 7 days |
| Cookie | fb_session, HttpOnly, SameSite=Strict, Path=/api, and Secure over HTTPS |
| Sign out | POST /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.
{
"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"
}
}| Rule | Value |
|---|---|
| Key format | fbk_ plus 48 hex characters |
| Key id format | key_ plus 12 hex characters. The id is not secret. |
| Label | Optional. Cut to 60 characters. Empty becomes unnamed. |
| Active keys | 5 per account. A sixth call returns 409 key_limit. Revoked keys do not count. |
| Rate-limit cost | 0 units |
| Daily limit | 20,000 units per key per UTC day. See rate limits. |
| Storage | SHA-256 hash only. A lost key cannot be recovered. Create a new one. |
#Send a key
Use either header. Authorization is the documented form.
| Header | Example |
|---|---|
Authorization | Authorization: Bearer fbk_... |
x-api-key | x-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
| Access | Endpoints |
|---|---|
| No key | All read endpoints for chains, gas, prices, transfers, wallets, tokens, transactions, labels, markets and status. Also Validate a rule, the /auth endpoints. |
| Session | Account, Create an API key, Revoke a key, Link Telegram, Unlink Telegram. A key is not accepted for these. |
| Key or session | Current key, Revoke the current key, and every /rules endpoint except /rules/validate. |
The live stream at /api/live reads no key.
#Failure modes
| HTTP | Code | When |
|---|---|---|
| 401 | missing_key | The endpoint requires a key and the request has none. |
| 401 | invalid_key | A key was sent, and it is unknown or revoked. |
| 401 | sign_in_required | The endpoint needs a session and the request has none. |
| 401 | invalid_challenge | The nonce expired, was used, or belongs to another address. Start again. |
| 401 | invalid_signature | The 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.
{
"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.
| Call | Auth | Effect on rules |
|---|---|---|
DELETE /keys/{id} | Session | Rules stay on and stay with the account |
DELETE /me | The key itself | Turns 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.