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:
{
"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
extrapayload 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:
curl "{tenant-host}/v1/admin/audit?limit=20&offset=0" \
-H "Authorization: Bearer {mgmtToken}"{
"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 400 — this 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):
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:
: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:
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.ndjsonBecause 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
API reference: list audit entries
The GET /v1/admin/audit operation — parameters, response schema, and status codes.
API reference: audit event stream
The GET /v1/admin/audit/events SSE operation in full.
RBAC: roles and permissions
Grant audit:read to a console role so a member can see the trail.
Security model
Where the tamper-evident hash chain fits in SenseCrypt's security guarantees.
Rotating signing keys
Operator how-to for rotating a tenant's OIDC and SAML signing keys — one-shot OIDC rotate vs make-before-break SAML staging, the overlap (grace) windows, staged certs by kid, KMS vs software custody, and what your relying parties and service providers must do during the overlap.
Testing your integration
Test your app against SenseCrypt in a dev environment — read email verification PINs from the logs when SES is off, run with app attestation disabled, and drive the real OIDC ceremony against a throwaway tenant.