Skip to content

Audit log

The audit log answers “who did what, to what, when, from where, and with what outcome.” It is the one record in hlix that carries a guarantee: every entry is written, no entry can be edited or deleted, and each Workspace’s entries are hash-chained, so a tampered or missing entry is detectable.

hlix keeps three separate records, and the difference between them is the point.

Record Answers Delivery
Audit log Who did what, to what, when, from where, with what outcome Guaranteed and immutable
Events What is happening right now Best-effort
Agent runs and logs Why a run cost or failed what it did Best-effort

Every action has a stable resource.verb name — project.created, secret.config.updated, task.executed — and each name maps to one of seven categories you can filter on.

Category What lands here
create New projects, tasks, cycles, members, collaborators, customer accounts, invoices, secrets, environments, agents, packs, skills, memories, deployments, integrations, and a deliverable approval being requested
update Changes to any of the above, plus settings, billing, provider keys, model and harness preferences, DNA, and every deliverable transition — publishing, unpublishing, approval and changes-requested are all recorded as updates
delete Removal of any of the above
execute Work being run: task and cycle execution, dispatch, deploys, approvals resolved, policy decisions, sandbox actions, integration runs
auth Sign-in, sign-out, API key created, API key revoked, passkey added
read The deliberately narrow set of reads worth recording — billing and secret access
export Data leaving the Workspace: invoice exports and data exports

Reads are not logged in general. Only billing and secret access are recorded, because there the read itself is the sensitive act.

Each entry carries the actor (a person, an agent, or the system, with the acting user attributed where an agent acted on someone’s behalf), the resource and its parent, the change itself, the source and calling IP and user agent, the outcome and severity, an optional reason, and the correlation ids below.

Before an entry is written, its change and metadata are redacted:

  • Any field whose name looks like a credential — anything matching secret, token, password, API key, authorization, cookie or credential — becomes [redacted].
  • Any value that looks like a credential is replaced by shape, not by name: provider key prefixes, JSON web tokens, bearer tokens, connection strings carrying a password, private-key blocks, and labelled key/value pairs.
  • Long strings are truncated.

Redaction is a safety net, not a reason to put credentials in a payload: an encoded, split or unfamiliar value may not match.

Terminal window
curl -sS "$HLIX_BASE_URL/v1/api/audit-log?category=delete&limit=25" \
-H "x-api-key: $HLIX_API_KEY" \
-H "X-Organization-Id: $HLIX_WORKSPACE_ID"

Expected result: {"items": [...], "nextCursor": "…"} — entries newest-first, and a cursor that is null on the last page.

Filter on actorType, actorId, action, category, resourceType, resourceId, outcome, severity, source, requestId, traceId, and a from/to time range. limit defaults to 50 and is capped at 200.

Paging is keyset, not offset: pass the previous page’s nextCursor back verbatim. The cursor is opaque — never construct one.

A project-scoped listing is available at /v1/api/audit-log/projects/{projectId}, and it is still owner/admin only. Narrowing what you can read does not widen who may read it.

Terminal window
curl -sS "$HLIX_BASE_URL/v1/api/audit-log/$ENTRY_ID" \
-H "x-api-key: $HLIX_API_KEY" \
-H "X-Organization-Id: $HLIX_WORKSPACE_ID"

Expected result: the entry plus a correlated object holding the events, agent runs and logs that share its trace.

Three correlation ids make that join possible. requestId identifies one API call. traceId identifies one unit of work across all three records. spanId identifies a step within it. An entry with no trace comes back with an empty correlated object rather than an error.

Two independent mechanisms sit under the guarantee.

Append-only storage. Entries cannot be updated or deleted by hlix or anyone else. Expiring old entries at the end of the retention period never rewrites current ones.

A per-Workspace hash chain. Each entry is hashed together with the hash of the previous entry in that Workspace. Removing an entry breaks the chain; altering one changes its hash. Check it:

Terminal window
curl -sS "$HLIX_BASE_URL/v1/api/audit-log/verify" \
-H "x-api-key: $HLIX_API_KEY" \
-H "X-Organization-Id: $HLIX_WORKSPACE_ID"

Expected result on an intact chain:

{"ok": true, "checked": 1842, "total": 1842, "firstBadId": null}

Compliance-critical changes commit with their record

Section titled “Compliance-critical changes commit with their record”

For the subset of mutations where the record matters as much as the change — provider key changes, secret configuration, environment configuration, and archiving, unarchiving or deleting a project — the change and its audit entry are saved together, atomically. The record cannot exist without the change, or the change without the record.

For every other action the entry is written as part of handling the request, but a failure to write it does not fail the request.

Record Kept for
Audit log 24 months
Events (the activity feed) 6 months
Execution logs 30 days
Agent runs Not pruned — kept for the life of the Workspace

Expired entries are removed daily. Retention periods are the same for every Workspace.

If the audit log does not answer what you expect

Section titled “If the audit log does not answer what you expect”
  • {"error": "admin only"} with 403. Every audit route needs owner or admin. A member gets this, and so does a client collaborator. There is no narrower view to fall back to.
  • {"error": "Audit entry not found"} with 404. The id does not exist in this Workspace. Confirm the X-Organization-Id header matches the Workspace the entry came from — an entry from another Workspace is not-found, not forbidden.
  • {"ok": false, ...} with a firstBadId. An entry does not hash to its recorded value. Capture the response, stop writing to that Workspace, and escalate — this is the condition the chain exists to surface.
  • ok is true but checked is less than total. The walk ran out of chain before it ran out of entries. Treat it exactly as the case above.
  • A filtered query returned everything. An action the server does not know is ignored rather than rejected. Check the spelling against the API reference.
  • A change you made is not in the log. Check the category first — general reads are not recorded by design, and only billing and secret access are. If it was a write, look for it under the resource’s own action name rather than the screen you made it from.
  • An entry has no correlated events or logs. Those two records are best-effort and are pruned sooner (6 months and 30 days). An older audit entry outliving its correlated records is expected.
  • An agent appears as the actor and you want the person behind it. Read the attributed user on the entry; an agent acting on someone’s behalf records both.
If you want to… Read
See the exact request and response shapes Audit log API
Understand who may read this Roles & permissions
See the gates that produce these records Review & QA gates
Read it in the dashboard The dashboard