SenseCrypt Docs
Guides

Audit log

Read, page, live-stream, and export the console-governance audit trail in the admin console or the Management API — and understand its tamper-evidence.

The audit log is your account's console-governance trail: a who-did-what record of every mutating action taken in the admin console — members invited, roles changed, passwords reset, apps and users created or deleted, signing keys rotated. It is append-only and tamper-evident (every entry is linked into a per-account hash chain), and it is scoped to the tenant you are viewing. This guide shows you how to read it, page through it, stream new entries live, and pull it out for archival.

Audit log ≠ Activity. The audit log (/v1/admin/audit) records administrator actions in the console. The separate Activity page (/v1/admin/activity) records end-user sign-in telemetry — the OAuth/OIDC logins your relying parties see. If you're looking for "who logged into my app", that's Activity, not the audit log.

Before you start

Reading the audit log requires the audit:read capability. The account owner holds it implicitly, and so does the built-in Viewer role (it carries every read capability). A member on a custom console role only sees the audit log if that role explicitly includes audit:read — see RBAC: roles and permissions. The same capability gates every path below, console and API alike.

The audit log is always tenant-scoped: you read one tenant's trail at a time, and every request must be made on that tenant's own host ({tenant-host}), never the canonical admin host. In the console this is automatic — the page shows the tenant you have selected; switch tenants to switch trails.

What an audit entry contains

Every fact the page renders is denormalised onto the row, so a deleted actor or target still displays correctly. An entry looks like this:

one audit entry
{
  "id": "b2f3a1c4-5d6e-4f70-8a91-0c2d3e4f5a6b",
  "actor_kind": "member",
  "actor_email": "ops@example.com",
  "action": "administration.member.invited",
  "target_type": "administration_member",
  "target_id": "9c8b7a65-4321-4d0e-9f8a-1b2c3d4e5f60",
  "target_label": "newhire@example.com",
  "extra": { "admin_role": "Viewer" },
  "created_at": "2026-07-16T12:00:00Z"
}

Prop

Type

Read and page the trail

Entries come back most recent first. The response is a page envelope — items, plus a total count for the whole tenant and the limit/offset you asked for — so you can build a pager.

Open Monitoring → Audit Log in the console. The page loads the selected tenant's most recent entries, 20 to a page.

Read the table top-to-bottom (newest first). Each row shows the actor, the action, the target, and when it happened.

Click a row to open its detail drawer — audit entries carry a structured extra payload the flat table can't show. Use Copy payload to copy that extra object as formatted JSON.

Use the pager at the bottom to move through older entries. The range label ("1–20 of 137") reflects the tenant's full total.

To read a different tenant's trail, switch the selected tenant — the page reloads scoped to that tenant's host.

Screenshot: the Audit Log page under Monitoring, showing the newest-first entry table, the pager, and one row's expanded detail drawer with its extra payload and Copy payload button.

There is no server-side filter. The audit log has no by-actor, by-action, or by-date-range search — you narrow it by selecting the tenant (which sets the scope) and by paging. To slice it further, export it (below) and filter the JSON offline.

GET {tenant-host}/v1/admin/audit returns a page of entries, newest first. Authenticate with a machine-to-machine access token whose bound role carries audit:read (see Machine-to-machine for how to obtain {mgmtToken}), and call it on the tenant's own host:

list the most recent audit entries
curl "{tenant-host}/v1/admin/audit?limit=20&offset=0" \
  -H "Authorization: Bearer {mgmtToken}"
200 OK
{
  "items": [
    {
      "id": "b2f3a1c4-5d6e-4f70-8a91-0c2d3e4f5a6b",
      "actor_kind": "member",
      "actor_email": "ops@example.com",
      "action": "administration.member.invited",
      "target_type": "administration_member",
      "target_id": "9c8b7a65-4321-4d0e-9f8a-1b2c3d4e5f60",
      "target_label": "newhire@example.com",
      "extra": { "admin_role": "Viewer" },
      "created_at": "2026-07-16T12:00:00Z"
    }
  ],
  "total": 137,
  "limit": 20,
  "offset": 0
}

Prop

Type

Called on the canonical admin host instead of the tenant's own host, the request fails with 400this endpoint is tenant-scoped; call it on the tenant's own host. A token missing audit:read gets 403 forbidden with the missing capability in the error body. A token minted for a different tenant, presented on this tenant's host, is a uniform 404 (no existence leak).

Stream new entries live

New entries arrive over Server-Sent Events (SSE), the same live transport the Activity page uses.

In the console, the Audit Log page opens the stream for you: while you are on the first page, new entries appear at the top the instant they happen — no refresh. (Live deltas only land on page 0; on older pages the window would shift under you, so the stream pauses there and the next page fetch reconciles the count.)

To consume the stream programmatically, subscribe to GET {tenant-host}/v1/admin/audit/events. SSE has no request-header channel, so a machine caller passes the token as the ?token= query parameter (not an Authorization header):

subscribe to the live audit stream
curl -N "{tenant-host}/v1/admin/audit/events?token={mgmtToken}"

Each new entry is delivered as an audit event; the server also emits :heartbeat comment lines (roughly every few seconds) to keep the connection alive:

stream frames
:heartbeat

event: audit
data: {"idp_admin_id":"…","tenant_id":"…","id":"b2f3a1c4-…","actor_kind":"member","actor_email":"ops@example.com","action":"application.created","target_type":"oauth_client","target_id":"…","target_label":"Billing Portal","extra":null,"created_at":"2026-07-16T12:03:11Z"}

The data payload is one audit entry plus its routing keys (idp_admin_id, tenant_id); the stream is filtered to the tenant named by the host, so you only receive that tenant's events. The same audit:read capability gates the stream.

Export the trail

There is no one-click export or CSV endpoint. You export the audit log by paging the list API and collecting the JSON — the response is already the full, denormalised record of each entry, so the pages are the export.

Walk offset forward in limit-sized pages (up to the 500-per-page cap) until you have total entries, and write each page's items to a file. For example, pulling everything into newline-delimited JSON with jq:

export a tenant's full audit log to NDJSON
offset=0
limit=500
while : ; do
  page=$(curl -s "{tenant-host}/v1/admin/audit?limit=$limit&offset=$offset" \
    -H "Authorization: Bearer {mgmtToken}")
  echo "$page" | jq -c '.items[]'
  count=$(echo "$page" | jq '.items | length')
  total=$(echo "$page" | jq '.total')
  offset=$((offset + count))
  [ "$count" -eq 0 ] && break
  [ "$offset" -ge "$total" ] && break
done > audit-export.ndjson

Because the trail is append-only, an export is a point-in-time snapshot: run it on a schedule (for example nightly) if you need a durable archive outside SenseCrypt.

How entries are protected from tampering

The audit log is append-only by design — there are no update or delete routes — but that alone doesn't stop a privileged operator with direct database access from editing or removing a row after the fact. To make any such change detectable, every entry is linked into a per-account hash chain: each row stores a SHA-256 row_hash computed over its own content plus the previous row's hash, so editing any field changes that row's hash, and deleting a row orphans its successor's back-link. Either kind of tampering breaks the chain and is caught by an integrity check that recomputes it and reports the first row that doesn't verify.

Chain verification is an internal integrity control, not an operator action. There is no console button or Management API endpoint to run the verification yourself — SenseCrypt performs it as part of the platform's own integrity monitoring. What you can rely on as an operator is the guarantee it backs: the audit trail is append-only, hash-chained per account, and any post-hoc edit or deletion is detectable. Read the security model for the full property.

Verify your setup

Confirm the trail is live and scoped correctly:

Open Monitoring → Audit Log for the tenant you want to watch, and stay on the first page.

In another tab, perform a small mutating action in that same tenant — for example, create a throwaway OIDC app or rename a group.

Watch the audit log: a new entry (e.g. application.created) should appear at the top within a moment, with your email as the actor and the object you touched as the target. That's the live stream and the who-did-what record working end to end.

Learn more

On this page