Skip to main content

Python reference

Drop-in Python module built on httpx. Tested against Python 3.11+.

import hashlib
import hmac
import json
import os
import time
import httpx

BASE = "https://api.flowbeacon.ai/api/public/v1"
API_PATH_PREFIX = "/api/public/v1"
API_KEY = os.environ["FLOWBEACON_API_KEY"]
ORG_ID = os.environ["FLOWBEACON_ORG_ID"]

BASE_HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
}


def sign_request(method: str, path: str, raw_body: 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}"


def call(client: httpx.Client, method: str, path: str, json_body=None):
# Serialise with compact separators so the signed body matches the wire body.
raw = json.dumps(json_body, separators=(",", ":")) if json_body is not None else ""
headers = {
**BASE_HEADERS,
"X-FB-Signature": sign_request(method, f"{API_PATH_PREFIX}{path}", raw),
}
r = client.request(
method,
f"{BASE}{path}",
headers=headers,
content=raw if raw else None,
)
body = r.json()
if not r.is_success or body.get("ok") is False:
raise RuntimeError(
body.get("error")
or body.get("detail")
or body.get("message")
or f"HTTP {r.status_code}"
)
return body.get("data", body)


def submit_and_wait(scenario_ids: list[str]):
max_attempts = 120
with httpx.Client(timeout=30) as client:
submit = call(
client, "POST", "/evaluate",
json_body={"scenario_ids": scenario_ids, "org_id": ORG_ID},
)
eid = submit["evaluation_id"]
for _ in range(max_attempts):
r = call(client, "GET", f"/evaluations/{eid}")
if r["status"] in ("complete", "error"):
return r
time.sleep(2)

raise TimeoutError(f"Timed out waiting for evaluation {eid}")


if __name__ == "__main__":
print(submit_and_wait(["4729318"]))

Notes

  • Compact JSON. Always serialise with separators=(",", ":"). The default json.dumps inserts spaces that won't match what most clients sign.
  • Body content vs. json=. This module passes the already-serialised body via content=.... Do not also pass json=...httpx would re-serialise it.
  • Use hmac.compare_digest if you ever verify signatures (e.g. for webhooks).