Skip to content
AbloAblo Docs
Esc
navigateopen⌘Jpreview
On this page

Audit log

Trace any committed change back to the key that made it, and to the person behind that key.

The audit log records who changed what in your org, and when — including changes an AI agent made on a person’s behalf. Every change is one row, and the rows are chained with a keyed hash (HMAC-SHA256) so you can later prove the history wasn’t altered. You can filter it, page through it, and export it.

Row shape

Each stored row carries both the attribution fields — who acted, on whose behalf, with which key — and the chain columns that make the log tamper-evident:

{
  id:                        string,
  occurredAt:                '2026-05-14T14:22:01.034Z',
  actorKind:                 'user' | 'agent' | 'system',
  actorId:                   string,
  onBehalfOfKind:            'user' | 'agent' | 'system' | null,
  onBehalfOfId:              string | null,
  capabilityId:              string | null,    // the API key/capability used for the write
  capabilityLabel:           string | null,    // its human-readable name, for scanning the log
  delegationChainRootUserId: string | null,    // always points at a human
  actionType:                'I' | 'U' | 'D',  // insert, update, delete
  modelName:                 string,           // the model that changed, e.g. 'orders'
  modelId:                   string,           // the row that changed
  confirmationState:         'auto' | 'previewed' | 'approved' | 'required_human_approval' | 'auto_historical',
  diffSummary:               unknown,
  // chain columns, carried on every stored row and checked by verify (below)
  chainSeq:                  number,
  prevHash:                  string,
  rowHash:                   string,
}

confirmationState records whether an agent’s write ran on its own (auto), was shown first (previewed), was signed off (approved), or is still waiting on a person (required_human_approval) — it’s also a filter on the list endpoint.

Delegation chain

Every action traces back to a human. Even when an agent makes the change, delegationChainRoot names the person who set that work in motion — there is no audit row whose root is an agent.

Verify

curl https://<your-app>/api/orgs/<slug>/audit/verify-chain?\
  principalKind=agent\
  &principalId=weather-agent-v3

Returns either:

{ "ok": true, "rowsChecked": 10472, "fromSeq": 1, "lastSeq": 10472 }

or, on tamper:

{ "ok": false, "brokenAtSeq": 8419, "reason": "hash_mismatch",
  "expectedHash": "a3f1c9…", "foundHash": "b7e04d…" }

Hashes come back as plain hex. expectedHash/foundHash accompany a hash_mismatch or prev_hash_mismatch; the other reasons (sequence_gap, missing_root, no_rows) stand on their own. Recomputing a row’s hash needs the org’s HMAC key, so verification runs where that secret is available.

Filter and paginate

The dashboard at /[orgSlug]/audit is the UI for this. The same filters are available on the API:

GET /api/orgs/<slug>/audit?actorKind=agent&since=2026-05-01&limit=100

Cursor-paginated. Continue with the nextCursor value from the response.

Export

curl 'https://<your-app>/api/orgs/<slug>/audit/export?actorKind=agent&since=2026-05-01' \
  > may-agent-writes.csv

One request exports CSV up to a hard row cap. If your window is larger than the cap, the response is truncated at the cap rather than erroring — so for large windows, split the window by date and request each slice, or page through the JSON GET endpoint above using nextCursor.

Compliance posture

The audit log landing page is the marketing-side description. The HMAC-SHA256 chain algorithm and its semantics live in the @ablo/audit-chain package — the reference implementation, embeddable if you need to verify chains in a detached service (given the org’s HMAC key).

Was this page helpful?