Authentication
Every authenticated request carries two independent credentials:
- An
Authorization: Bearer fb_live_...header (the API key). - An
X-FB-Signatureheader containing a signature you compute yourself over a canonical request string, using your API key as the signing secret.
The unauthenticated GET /governance/health endpoint is the only exception.
X-FB-Signature is not issued by FlowBeacon and is not returned by any endpoint. Your application builds it locally on every request: take the API key you already hold, assemble the canonical string below, and HMAC-SHA256 it. The same API key is both your bearer credential and your signing secret — so the only thing you ever need from us is the key itself. Copy-paste reference implementations for TypeScript, Python, and Go are at the bottom of this page.
Getting an API key
API keys are not issued through the public API — there is no Bearer-key or request-signature path to mint one. To get a key you must:
- Sign up for a FlowBeacon account and sign in to the console.
- Make sure you hold the admin or owner role in your organization. Key creation, rotation, and revocation are restricted to those roles; a member without them receives
403 Admin or owner role required. - Create the key from the console (or call the session-authenticated
POST /api/v1/keys, which relies on your browser's NextAuth session cookie — it does not accept a Bearer key or request signature).
The raw key is shown exactly once on creation and is never retrievable afterward — store it securely. These admin endpoints live at /api/v1/keys, outside /api/public/v1/*. See Rotating and revoking keys.
1. Bearer API key
Authorization: Bearer fb_live_<48 hex chars>
Keys are validated against an encrypted value stored server-side; the raw value is never retrievable after creation. Each request also checks that the key is active, not revoked, and not past expires_at.
Key format
| Part | Value |
|---|---|
| Prefix | fb_live_ |
| Body | 48 lowercase hexadecimal characters (24 random bytes) |
| Total length | 56 characters |
| Displayable prefix | First 12 characters (fb_live_xxxx) are safe to log or show in UI |
2. Request signing
Every authenticated request must carry X-FB-Signature. Requests without it return HTTP 403 Missing request signature. Malformed or stale signatures return HTTP 403 Invalid request signature.
Canonical signing string
message = "{timestamp}.{HTTP_METHOD}.{request_path}.{raw_request_body}"
timestamp— current Unix time in seconds.HTTP_METHOD— uppercase, for examplePOSTorGET.request_path— the path component only, including the leading/api/public/v1/.... No query string.raw_request_body— the exact bytes sent in the request body, or empty string for bodyless requests.
Signing and header
- Signing key: the raw API key string itself.
- Signature: sign the canonical string with your API key to produce a lowercase hex value (see the reference implementations below).
- Header format:
X-FB-Signature: t=<unix>,v1=<hex>
X-FB-Signature: t=1714564800,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
Tolerance window
The timestamp must be within 300 seconds of server time. Requests outside this window are rejected with 403 Invalid request signature. This prevents replay attacks.
Reference implementations
- TypeScript
- Python
- Go
import crypto from 'node:crypto';
export function signRequest({
method,
path, // e.g. '/api/public/v1/evaluate'
rawBody, // exact bytes you will send
apiKey,
}: {
method: string;
path: string;
rawBody: string;
apiKey: string;
}): string {
const timestamp = Math.floor(Date.now() / 1000);
const message = `${timestamp}.${method.toUpperCase()}.${path}.${rawBody}`;
const signature = crypto.createHmac('sha256', apiKey).update(message).digest('hex');
return `t=${timestamp},v1=${signature}`;
}
import hashlib, hmac, time
def sign_request(method: str, path: str, raw_body: str, api_key: str) -> str:
ts = int(time.time())
msg = f"{ts}.{method.upper()}.{path}.{raw_body}"
sig = hmac.new(api_key.encode(), msg.encode(), hashlib.sha256).hexdigest()
return f"t={ts},v1={sig}"
package fb
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strconv"
"time"
)
func SignRequest(apiKey, method, path string, rawBody []byte) string {
ts := strconv.FormatInt(time.Now().Unix(), 10)
msg := ts + "." + method + "." + path + "." + string(rawBody)
mac := hmac.New(sha256.New, []byte(apiKey))
mac.Write([]byte(msg))
return "t=" + ts + ",v1=" + hex.EncodeToString(mac.Sum(nil))
}
The server uses constant-time comparison. Signature mismatches return HTTP 403 Invalid request signature. A missing header returns HTTP 403 Missing request signature.
JSON.stringify and Python json.dumps are not byte-stable across runtimes. Always sign the exact bytes you will send — serialise once, sign that string, transmit that string.
3. IP allowlisting
Each API key supports an optional list of allowed source IPs (IPv4 or IPv6). When set, requests from any other IP return HTTP 403 IP address not allowed. The client IP is taken from the first entry of X-Forwarded-For when present, otherwise the direct peer address.
Leaving the allowlist empty disables this check for the key. See the IP allowlisting guide.
4. Transport security
- Only HTTPS is accepted in production.
- TLS 1.2 or higher is required.
- Browser-origin requests are blocked by default; this API is intended for server-to-server usage. Partners requiring browser access during development should contact support.
5. Key rotation
Use the web console or POST /keys/{id}/rotate to rotate a key. Rotation creates a replacement with identical configuration and schedules the old key to expire after a configurable grace period (default 24h, max 168h). During the grace period both keys work, allowing zero-downtime deploys. See Rotating and revoking keys.
6. Webhook signatures (different signing key)
Webhook deliveries use the subscription's signing_secret, not the API key, and the canonical string is shorter: "{timestamp}.{raw_body}" (no method, no path — the receiver controls the URL). See Handle webhooks.