Skip to main content

Read the audit trail

GET /scenarios/{scenario_id}/audit-trail returns the governance history of a single automation, newest first. It answers "who changed this scenario's compliance posture, when, and what happened" — the evidence you export for an audit, or render as an activity feed next to a canvas.

Results come from FlowBeacon's append-only, hash-chained governance event log. Rows are never updated or deleted in place, so a trail you fetched yesterday still reads the same today.

What the trail contains

One entry per chained governance event recorded against the scenario:

event_typeWhat it records
policy_evaluationA governance evaluation ran. summary.results_summary carries the pass/fail/warn/n-a counts.
policy_assignment_changeA policy was assigned to, or removed from, this automation.
remediation_attemptA remediation was applied. summary.changes_summary names the policies that moved, e.g. 1 policy improved (SEC-5: fail → pass).
remediation_syncA remediation was synced back to the source platform.
blueprint_uploadedThe automation was first imported into FlowBeacon.
blueprint_updatedA new revision of the automation was ingested.

Every entry carries id, event_type, created_at, actor, and summary. Security events (cross-org access attempts, org-header mismatches) are on a separate chain and are never returned here.

What is not in the trail

  • Regulation-change ratifications are excluded. When FlowBeacon ratifies an updated regulatory citation, that is recorded as global telemetry with no organization or automation linkage — there is no join today from a ratified citation to the automations it affects. Filtering by event_type=regwatch.ratify therefore returns an empty events array rather than an error. If you need regulation-change provenance per automation, track the policy_code values in policy_evaluation entries and map them yourself.
  • Raw blueprint snapshots and per-policy evidence are not exposed. The summary object is an allowlist. Blueprint parameters and mapper values can contain credentials and webhook URLs, so they are structurally excluded. For current violation detail, call GET /scenarios/{scenario_id}/results.
  • Nothing from another organization. The scenario id is resolved inside your API key's organization only. An id that belongs to someone else returns the same 404 Scenario not found as an id that does not exist.

Ordering and retention

  • Ordering is created_at DESC, id DESC — newest first, with the event id as a deterministic tie-breaker so two events written in the same microsecond never swap places between pages.
  • created_at is an ISO 8601 UTC timestamp (2026-08-20T10:30:00+00:00).
  • Retention is 1 year for governance events by default. Organization lifecycle and RBAC events are retained for 7 years, and low-value informational telemetry for 90 days — but those classes do not appear in a per-automation trail. Export anything you need to keep beyond a year.
  • id is stable and unique. Use it to de-duplicate if you re-fetch overlapping windows.

Paging with a cursor

Pagination is keyset, not offset. Offset paging on an append-only stream skips and duplicates rows whenever a write lands between two of your requests.

  1. Call the endpoint with no cursor.
  2. Read data.next_cursor.
  3. If it is non-null, call again with ?cursor=<that value>.
  4. Stop when next_cursor is null.

Treat the cursor as opaque — do not parse, construct, or reuse one across different filter values. A cursor FlowBeacon did not issue returns 422.

First page
{
"ok": true,
"data": {
"scenario_id": "4729318",
"blueprint_id": "dddddddd-dddd-dddd-dddd-dddddddddddd",
"events": [
{
"id": "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee",
"event_type": "remediation_attempt",
"created_at": "2026-08-20T10:30:00+00:00",
"actor": { "name": "Ada Lovelace", "email": "ada@example.com" },
"summary": {
"policies_evaluated": 3,
"policies_passed": 2,
"policies_failed": 1,
"overall_result": "improved",
"notes": "Auto-remediation applied",
"changes_summary": "1 policy improved (SEC-5: fail → pass)",
"compliance_transition": {
"from": "non_compliant",
"to": "compliant",
"became_compliant": true
},
"policy_code": "SEC-5"
}
}
],
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wOC0yMFQxMDozMDowMCswMDowMCJ9"
},
"meta": { "watermark": "wm_a1b2c3d4" }
}

Signing a paged request

Every authenticated request needs Authorization: Bearer and X-FB-Signature. The canonical signing string is {timestamp}.{METHOD}.{path}.{body} and the path excludes the query string (see Authentication) — so every page of the trail signs the same path, and adding ?cursor=… does not change the signature you compute.

Page 1
API_KEY="fb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
REQ_PATH="/api/public/v1/scenarios/4729318/audit-trail"
TS=$(date +%s)
SIG=$(printf '%s' "$TS.GET.$REQ_PATH." | openssl dgst -sha256 -hmac "$API_KEY" -hex | awk '{print $NF}')

curl -s "https://api.flowbeacon.ai$REQ_PATH?limit=50" \
-H "Authorization: Bearer $API_KEY" \
-H "X-FB-Signature: t=$TS,v1=$SIG"
Page 2 — same path, same signing string, new cursor
CURSOR="eyJjcmVhdGVkX2F0IjoiMjAyNi0wOC0yMFQxMDozMDowMCswMDowMCJ9"

curl -s -G "https://api.flowbeacon.ai$REQ_PATH" \
--data-urlencode "limit=50" \
--data-urlencode "cursor=$CURSOR" \
-H "Authorization: Bearer $API_KEY" \
-H "X-FB-Signature: t=$TS,v1=$SIG"
note

$TS must be within 300 seconds of server time, so recompute TS and SIG per request in a real client rather than reusing them across a long page walk.

Filtering

Only evaluations, for one week
curl -s -G "https://api.flowbeacon.ai$REQ_PATH" \
--data-urlencode "event_type=policy_evaluation" \
--data-urlencode "from=2026-08-01T00:00:00Z" \
--data-urlencode "to=2026-08-08T00:00:00Z" \
-H "Authorization: Bearer $API_KEY" \
-H "X-FB-Signature: t=$TS,v1=$SIG"

An unusable filter is rejected, never dropped: an unknown event_type, an unparseable from/to, a foreign cursor, or a limit outside 1–200 all return 422. A bad value can never silently widen the window behind a 200.

Verifying the chain yourself

Add include_chain=true to attach each event's hash-chain evidence — seq, hash_version, prev_hash, row_hash — for archival. It is off by default: it is verification machinery, not application data, and it roughly doubles the response size.

{
"id": "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee",
"event_type": "remediation_attempt",
"created_at": "2026-08-20T10:30:00+00:00",
"actor": { "name": "Ada Lovelace", "email": "ada@example.com" },
"summary": { "…": "…" },
"chain": {
"seq": 41,
"hash_version": 3,
"prev_hash": "abab…",
"row_hash": "cdcd…"
}
}