CortexDB Docs
API Reference

/v1/audit

The append-only, hash-chained audit log — list rows, fetch one, and verify integrity.

Every policy decision and capability-gated action is recorded to an append-only, SHA-256 hash-chained audit log. Each row's prev_hash equals the prior row's row_hash, so tampering is detectable.

Set CORTEX_V1_AUDIT_PATH or the audit log is lost on every restart

By default the audit log is kept in memory only: a graceful restart (docker stop -t 120, then start) empties GET /v1/audit, and the default boot log says nothing about it. Set CORTEX_V1_AUDIT_PATH to a file on the data volume (for example /data/audit.jsonl) and the server writes the rows there as JSONL and keeps them across restarts; the boot log then says v1 audit log: persistent (JSONL) path=…. A path the server can't open falls back to in-memory with an ERROR log line. This applies with or without an API key. Treat the setting as required for any deployment that relies on the audit trail.

Audit row shape

{
  "id": "audit_01HX...",
  "ts": "2026-05-15T10:42:01Z",
  "actor": "user:alice",
  "capability": "forget.gdpr",
  "decision": "allow",
  "decided_by_tier": "scope",
  "endpoint": "POST /v1/experience",
  "response_status": 202,
  "elapsed_ms": 12,
  "gdpr": false,
  "request": {
    "method": "POST",
    "path": "/v1/experience",
    "body_bytes": 412,
    "body_hash": "sha256:...",
    "headers_hash": "sha256:...",
    "tenant_pepper_version": 1
  },
  "request_id": "req_01HX...",
  "tenant_id": "...",
  "token_jti": "...",
  "row_hash": "sha256:...",
  "prev_hash": "sha256:..."
}

Row fields

The request block is { body_bytes, body_hash ("sha256:…"), headers_hash, method, path, tenant_pepper_version }. The authentication layer's rows carry no scope; a row a route writes for its own decision does, with decided_by_tier: "handler": a forget's (gdpr: true) and a derived-data re-derivation's or unmerge's. endpoint includes the HTTP method ("POST /v1/experience"). There is no event_id field; client_ip (the first X-Forwarded-For address) appears on the authentication layer's row only when the request carries that header, and only to callers that may read other actors' rows. row_hash and prev_hash form the hash chain. request_id is the request's X-Request-Id: since v0.10.2 the authentication layer's row and a forget's gdpr row carry the id the response returned (verified on v0.10.2).

Endpoints

  • GET /v1/audit → the row list (append-only, hash-chained). The query filters are decision, actor and capability. To narrow by time or endpoint, filter on the rows' ts and endpoint fields on your side.
  • GET /v1/audit/{id} → a single row.
  • POST /v1/audit/verify { audit_id, body } → { match, algorithm: "sha256", hashed_at, tenant_pepper_version }. match: true requires the exact canonicalized row body.

Which rows record a capability

With an API key or tokens, every request is audited. Requests allowed at the deployment tier are recorded with an empty capability (""), so ?capability= matches only rows decided at a finer tier (for example memory.forget on a forget); filter those others by actor, decision or endpoint. In no-auth mode (CORTEX_INSECURE_NO_AUTH=1) only operations such as forgets, which record their own decision, produce rows.

On this page