Back to Documentation

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_hash to 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”).