Skip to main content

Authentication

Every authenticated request carries two independent credentials:

  1. An Authorization: Bearer fb_live_... header (the API key).
  2. An X-FB-Signature header 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.

You generate the signature — there is nothing to fetch

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:

  1. Sign up for a FlowBeacon account and sign in to the console.
  2. 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.
  3. 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

PartValue
Prefixfb_live_
Body48 lowercase hexadecimal characters (24 random bytes)
Total length56 characters
Displayable prefixFirst 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 example POST or GET.
  • 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

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}`;
}

The server uses constant-time comparison. Signature mismatches return HTTP 403 Invalid request signature. A missing header returns HTTP 403 Missing request signature.

Sign the bytes you send

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.