audit_log. The
console’s Audit log screen is a thin reader over two tRPC queries:
adminAudit.list for the entries and adminAudit.options for the
filter vocabularies. Both are scoped to a single org, so a caller only
ever sees the audit trail of the org it passes as orgId.
What gets written and when
An entry is appended when an admin mutation completes. The write is a side effect of the mutation, not something you trigger — there is no “create audit entry” procedure in this surface. The action string on the row identifies the mutation that produced it, andresource_type plus
resource_id identify what it touched.
Entries fall into two shapes:
- Resource mutations — a
resource_idis set, andbefore/aftercarry the JSON state of that resource on either side of the change. - Events without a single target —
resource_idisnull. Bulk operations and session events look like this.
The audit entry record
adminAudit.list returns rows, an array of records with the following
fields, plus total, the count of entries matching the filters (not the
count returned on this page).
before and after are opaque JSON. Their shape follows the resource
they describe, so do not assume a common schema across resource_type
values.
Action and resource type vocabulary
Neither vocabulary is a fixed enum. Both are derived from the entries that actually exist in the org, and the console fetches them fromadminAudit.options:
Build filter UIs from this call rather than hardcoding a list — an org
that has never deactivated anything will not have a deactivation action
in its vocabulary.
Action strings follow verb conventions that the console relies on to
colour each timeline dot. Severity is not stored on the row; it is
inferred client-side from the action string, which is the source of
truth:
If you write your own reader, reproduce this mapping or ignore it — the
row carries no severity column either way.
Actors, IP and request id
actor_user_id is the acting user. When a mutation originates from the
platform rather than a person, it is null; the console renders those
entries as system. actor_label is a denormalised display name
captured at write time, so it survives later renames or user deletion —
prefer it for display and fall back to actor_user_id, then system:
ip is the source address of the request that caused the write, and is
shown inline next to the actor. request_id is on the record but the
console timeline does not render it; use it to join an audit entry back
to application logs and traces for the same request.
Reading before and after diffs
Expanding an entry renders a JSON diff ofbefore against after. The
console only renders the diff panel when at least one of the two is
non-null, so entries that carry neither expand to nothing.
Read the two fields together to classify the change:
Both values are stored as JSON, so over the API you can diff them with
whatever structural diff library you already use.
Filtering, search and paging
adminAudit.list accepts these inputs. Every filter is optional except
orgId, and omitting one means “any”.
The console’s Range chip is a convenience over
since: Today
sends local midnight, Last 7 days and Last 30 days send now minus
that many days, and All time omits since entirely.
Paging is page-number based. total is the number of entries matching
the filters across all pages, so compare it against the rows you already
hold to decide whether another page exists:
keepPreviousData.
The console caches the adminAudit.options response for 60 seconds, so a
brand-new resource_type may take that long to appear in the filter
dropdowns after its first entry is written. Query options directly if
you need it fresh.
Aggregating a filtered window
The console’s right-hand rail — top actors and action mix — is computed in the browser from the rows currently loaded, not from the whole filtered range. That is why it shows Loaded and Total in range as separate numbers. If you need aggregates over an entire range, page throughlist yourself and aggregate the full result; do not read a
single page’s distribution as representative of the range.
Immutability and retention
Audit entries are append-only. The surface behind this screen exposesadminAudit.list and adminAudit.options, both read-only queries —
there is no procedure to edit, redact or delete an entry, and the console
offers no such control. Treat a written row as final, including its
before / after payloads.
Because entries are immutable, the practical bound on how far back you
can read is whatever the org’s audit data retention allows. Use
since: undefined (the All time range) to read the oldest entries
still available, and export anything you need to keep beyond that
outside the platform.
Related
- Authentication — API keys and the scopes that gate control-plane mutations
- Telemetry — traces carry the same request correlation ids you see on audit entries