Skip to content

The audit log

What the Overlane audit log records, how its hash chain shows tampering, and how to read, verify and export it from the web app or the API.

Updated View as MarkdownOpen in ChatGPTOpen in Claude

The audit log lists every admin, policy and token action in your workspace: who did it, what it touched, when, and whether it worked. Owners and admins read it in Settings > Audit log. Nobody else can.

What is recorded

One row per action, with these fields and nothing else: the time, the actor (a person’s ID or an agent token’s ID), whether the actor was a user or an agent, their role, the action, its category, the target’s type and ID, and the result.

Result Meaning
success It happened.
denied The role or tenant check refused it. A forced request from a viewer shows up here.
failure It was allowed, but the service could not do it.
const categories = new Set(Object.values(AUDIT_ACTIONS));
assert.deepEqual([...categories].sort(), ["admin", "policy", "token"]);
assert.equal(AUDIT_ACTIONS["token.created"], "token");
assert.equal(AUDIT_ACTIONS["member.role_changed"], "admin");
assert.equal(AUDIT_ACTIONS["release.promoted"], "policy");
Category Examples
admin member.removed, member.role_changed, invitation.created, invitation.revoked, audit.exported, data.exported, channel.promoted
policy run_policy.updated, project.updated, release.published, release.promoted, release.withdrawn, release.pinned, release.rolled_back, memory_settings.updated, issue_sync.updated, loop.created, loop.updated, loop.deleted, bot.registered, bot.revoked, bot.key_rotated, feature_override.set, feature_override.cleared, usage_policy.updated, budget.set, autonomy.changed, decision_settings.updated, alert_rule.updated, webhook.created
token token.created, token.revoked, api_key.created, api_key.revoked, ci_secret.set, connector.connected

The list is not complete; the log records every action in these three categories.

A row holds identifiers only. It never contains a request body, a token secret, an invitation link or memory text.

Tamper evidence

Rows of a workspace form a hash chain. Each row’s hash covers the previous row’s hash and its own fields, and rows are numbered 1, 2, 3 with no gaps. Edit a row, remove one or reorder them and the chain no longer checks out. The check is a pure function, the same one the service, the web app and an offline reader use.

assert.equal(verifyAuditChain("tm-1", AUDIT_FIXTURE).ok, true);
const edited = AUDIT_FIXTURE.map((row, i) => (i === 1 ? { ...row, actor: "someone-else" } : row));
const result = verifyAuditChain("tm-1", edited);
assert.equal(result.ok, false);
assert.equal(result.brokenAt, 2);
assert.equal(result.reason, "bad_hash");
const removed = AUDIT_FIXTURE.filter((_, i) => i !== 1);
assert.equal(verifyAuditChain("tm-1", removed).reason, "bad_sequence");

A chain cannot show that the newest rows were cut off, since a shorter chain still verifies. To notice that, keep the head hash from an earlier export or check; the log shows the current head.

Read, filter, verify and export

In the app, filter by category and result, press Verify log to check the whole chain, and press Export to download every row.

Through the API the same three reads exist for owners and admins: the audit list at /v1/tenants/{tenantId}/audit (filters category, result, action, and paging with after and limit), /v1/tenants/{tenantId}/audit/verify, and /v1/tenants/{tenantId}/audit/export.

The export is newline-delimited JSON, one row per line with its hashes, so you can check it later without the service:

const lines = AUDIT_FIXTURE.map((row) => JSON.stringify(row)).join("\n") + "\n";
const rows = lines.split("\n").filter(Boolean).map((line) => AuditEvent.parse(JSON.parse(line)));
assert.equal(rows.length, 3);
assert.equal(verifyAuditChain("tm-1", rows).ok, true);
assert.equal(auditPaths.export("tm-1"), "/v1/tenants/tm-1/audit/export");
Navigation

Type to search…

↑↓ navigate↵ selectEsc close

Overlane Agent

Ask about the docs. Answers are quoted from the pages, with links. This panel cannot change anything.

    Questions are answered in your browser from the docs index. Nothing you type here is stored or sent anywhere.