Skip to main content

Audit API

All routes require an audit-reader bearer (the dedicated reader token, or the admin token). Every route here is read-only — audit rows are written only by the decision path, and the database refuses to change them afterward.

GET /v1/audit/actions/{action_id}/chain​

The full delegation chain for one recorded action, back to the consenting human.

Response (ChainRead):

{
"root": {"user_sub": "user:alice", "scope": "kb-search-agent", "consented_at": "...", "session_id": "..."},
"hops": [
{"hop": 1, "action_id": "...", "acting_agent": "user:alice", "target": "mcp://kb.internal/search",
"target_kind": "tool", "data_class": "kb.public", "decision": "allow", "deny_reason": null,
"enforced": true, "ts": "..."}
],
"complete": true,
"break": null
}

When complete: false, break names at_hop and a reason (LEGACY_TOKEN, PARENT_NOT_FOUND, PARENT_MISMATCH, PARENT_INCOMPLETE, or LEGACY_RECORD) — see Query a delegation chain for what each means.

GET /v1/audit/decisions​

Filtered decision history, newest first, paginated, stable under concurrent appends.

Query params: limit, cursor, session_id, agent, outcome, deny_reason, enforced, target_kind, since, until (all optional filters)

Response (AuditDecisionPage): a Page of decision rows plus pagination info, same envelope shape as GET /v1/agents.

GET /v1/decisions — deprecated​

Kept for one phase for existing observe-mode tooling; served by the same query as GET /v1/audit/decisions with Phase 2's filter set (session_id, outcome, deny_reason, enforced). New callers should use GET /v1/audit/decisions instead. ?enforced=false&outcome=deny is the observe-mode report proper: everything that would have been blocked, while nothing was.