Sessions (Chained Audit Groups)
Sessions let you group multiple audit records into an ordered chain so you can reason about a workflow as one auditable unit.
What is a session?
A session is an ordered list of events that each reference an existing audit record (by record_hash / record_id). Factara maintains a session hash that updates as events are appended:
Session head (chain hash): session_hash = sha256(prev_session_hash + record_hash)
Because the session head depends on order, any missing/reordered event changes the computed hash and is detected.
When to use sessions
- You want to treat a multi-step agent workflow as a single auditable unit (e.g., plan → verify → authorize → act).
- You want a deterministic way to prove “these N decisions happened in this exact order” without stitching records manually.
- You want to fetch one object (
session_id) to review/verify an entire chain.
Sessions are not async jobs. If you need background execution, retries, and polling for completion, use a jobs system. Sessions are an audit grouping primitive, not a compute primitive.
Sessions API
All endpoints require Authorization: Bearer YOUR_API_KEY.
POST /v2/sessions
Create a new session. You can provide a session_id or let the server generate one.
Returns session_id and initial session metadata.
POST /v2/sessions/{session_id}/events
Append an audit record to the session.
Provide record_hash (and optionally audit_record_id) to link the existing record.
POST /v2/sessions/{session_id}/close
Close the session (idempotent).
Once closed, appends are rejected.
GET /v2/sessions/{session_id}
Fetch session metadata and events ordered by sequence.
The server verifies the chain on read; tampering yields an error (rather than silently returning invalid state).
Example: audit a multi-step workflow as one session
In this flow, we create a session, run multiple verify/authorize requests (each produces an audit record), append those records to the session, then close it.
Step 1 — create a session
POST /v2/sessions
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"label": "refund-workflow-1842",
"metadata": { "order_id": "1842" }
}
# Response (example)
{
"session_id": "sess_3b0c7f7a-9d1f-4a5f-9b1e-2df0d7d1f2a1",
"status": "active",
"event_count": 0,
"session_hash": "sha256:..."
}Step 2 — call verify/authorize as usual (each returns an audit handle)
POST /v2/authorize
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"action": { "type": "refund.approve", "params": { "order_id": "1842", "amount_usd": 550 } },
"claims": [
{ "id": "c1", "text": "Order #1842 was charged twice." }
],
"sources": [
{ "type": "text", "title": "Support ticket #1842", "content": "Order #1842 was charged twice; recommend full refund of $550." },
{ "type": "text", "title": "Refund policy", "content": "Refunds over $500 require manager approval unless duplicate charge is found." }
]
}
# Response (example excerpt)
{
"verdict": "allow",
"metadata": {
"audit": {
"record_id": "99ed92e7-8aaf-4e0f-b1b3-1a2b3c4d5e6f",
"record_hash": "sha256:32514406a64...",
"key_id": "audit_v1"
}
}
}Keep the record_hash (and optionally record_id). You’ll append this audit record into the session.
Step 3 — append the audit record into the session
POST /v2/sessions/sess_3b0c7f7a-9d1f-4a5f-9b1e-2df0d7d1f2a1/events
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"record_hash": "sha256:32514406a64...",
"audit_record_id": "99ed92e7-8aaf-4e0f-b1b3-1a2b3c4d5e6f",
"request_hash": "sha256:abc123",
"label": "authorize-refund"
}
# Response (example)
{
"session_id": "sess_3b0c7f7a-9d1f-4a5f-9b1e-2df0d7d1f2a1",
"seq": 0,
"session_hash": "sha256:NEW_HEAD...",
"event_count": 1
}Step 4 — fetch the session (server verifies chain integrity)
GET /v2/sessions/sess_3b0c7f7a-9d1f-4a5f-9b1e-2df0d7d1f2a1
Authorization: Bearer YOUR_API_KEY
# Response (example excerpt)
{
"session_id": "sess_3b0c7f7a-9d1f-4a5f-9b1e-2df0d7d1f2a1",
"status": "active",
"event_count": 1,
"session_hash": "sha256:NEW_HEAD...",
"events": [
{
"seq": 0,
"record_hash": "sha256:32514406a64...",
"audit_record_id": "99ed92e7-8aaf-4e0f-b1b3-1a2b3c4d5e6f",
"request_hash": "sha256:abc123",
"label": "authorize-refund"
}
]
}Step 5 — close the session
POST /v2/sessions/sess_3b0c7f7a-9d1f-4a5f-9b1e-2df0d7d1f2a1/close
Authorization: Bearer YOUR_API_KEY
# Response (example)
{
"session_id": "sess_3b0c7f7a-9d1f-4a5f-9b1e-2df0d7d1f2a1",
"status": "closed",
"event_count": 1,
"session_hash": "sha256:NEW_HEAD..."
}Guidelines & gotchas
- Append is idempotent per record: adding the same
record_hashto the same session twice is rejected. - Order matters: the session hash commits to the exact sequence; insert steps in the order you want to prove.
- Sessions don’t replace Merkle proofs: session chaining proves ordered grouping; Merkle batching proves inclusion in a signed batch. You can use both.
- Keep sessions small and meaningful: prefer one session per workflow run (not a global “everything session”).
See also: Sources & Auditing · API Reference