audit
The audit log records who did what in a tenant; this group searches it, verifies its tamper-evident hash chain, and exports it for archiving.
The audit log records who did what in a tenant; this group searches it, verifies its tamper-evident hash chain, and exports it for archiving. Every provisioning operation, denial, sign-in, and access-lifecycle event is recorded. Compliance, incident response, and support all start with "who did what, when": the log answers that, and its hash chain lets you prove to an auditor that no record was altered or removed afterwards. See the audit chain.
What an audit event holds
Each event has an id, the tenant, the acting identity (actorId), the action (such as iam:groups:update,
auth:session:create, or binding:activate), the resourceId it concerned, the outcome (allow or deny),
and a timestamp. Depending on the event it also carries metadata, rootOverride when a root administrator
acted through the override, impersonatorId when an administrator acted in a "view as" session, and the session
the actor used. Events never contain passwords, tokens, or secrets.
Every event also has a place in the tenant's hash chain: sequence (its position, from 1), previousHash (the
hash of the event before it), and hash (SHA-256 over the event's canonical JSON). Changing, reordering, or
deleting a stored event breaks the chain at that point, which verify detects.
All three methods need iam:audit:read on the tenant, and each call is itself recorded as an iam:audit:read
event, so reading the log leaves a trace. The audit-verify and audit-export
CLI commands run the same checks and export with deployment access, and record
nothing.
| Method | What it does | Access |
|---|---|---|
export | Returns a page of the tenant's audit events in chain order as JSON Lines, ready to archive. | Credential |
list | Searches the tenant's audit events, newest first, by actor, action, resource, outcome, and time range. | Credential |
verify | Checks the tenant's audit hash chain and reports whether any stored event was altered, reordered, or removed. | Credential |
export
Returns a page of the tenant's audit events in chain order as JSON Lines, ready to archive.
Used inAudit chain,Operations recipes
- Permission:
iam:audit:readon the tenant. - Audited as:
iam:audit:read. - Errors:
INVALID_INPUTwhenfromSequenceis below 1 orlimitis outside 1 to 10 000.
body holds one JSON event per line, including sequence, previousHash, and hash, so an archive can be
verified later anywhere with verifyAuditChain from @better-iam/core. Start at fromSequence (default 1) and
follow nextSequence until it is undefined; each page's last hash links to the next page's first event. head
is the current end of the chain, useful for comparing against your archive. Archive pages as they are; exporting
before pruning keeps the full history verifiable.
let fromSequence: number | undefined = 1;
while (fromSequence !== undefined) {
const page = await iam.api.audit.export(credential, { tenantId, fromSequence, limit: 5000 });
if (page.count) await archive.append(`${page.body}\n`);
fromSequence = page.nextSequence;
}Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/audit/export" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.audit.export(
credential: CredentialInput,
input: { tenantId: string; fromSequence?: number; limit?: number },
): Promise<{
format: 'jsonl';
count: number;
body: string;
firstSequence: number | undefined;
lastSequence: number | undefined;
nextSequence: number | undefined;
head: { sequence: number; hash: string } | null;
}>list
Searches the tenant's audit events, newest first, by actor, action, resource, outcome, and time range.
Used inEvents and audit
- Permission:
iam:audit:readon the tenant. - Audited as:
iam:audit:read. - Errors:
INVALID_INPUTwhenoutcomeis notallowordeny, orlimit(1 to 1000),offset,from, ortois out of range.
action accepts a glob pattern such as iam:bindings:* or binding:*; actorId, resourceId, and outcome
match exactly; from and to bound the timestamp (epoch milliseconds). limit defaults to 100. Use it for an
activity feed, a person's history, or an investigation such as "every denial in the last hour".
const denials = await iam.api.audit.list(credential, {
tenantId,
outcome: 'deny',
from: Date.now() - 60 * 60 * 1000,
});Prop
Type
An array of AuditEvent.
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/audit/list" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.audit.list(
credential: CredentialInput,
input: {
tenantId: string;
limit?: number;
offset?: number;
actorId?: string;
action?: string;
resourceId?: string;
outcome?: 'allow' | 'deny';
from?: number;
to?: number;
},
): Promise<AuditEvent[]>verify
Checks the tenant's audit hash chain and reports whether any stored event was altered, reordered, or removed.
Used inEnterprise onboarding,Audit chain,Operations recipes
- Permission:
iam:audit:readon the tenant. - Audited as:
iam:audit:read. - Errors:
INVALID_INPUTwhenfromSequenceortoSequenceis below 1.
The check walks the events in sequence order: the sequences must be contiguous, each previousHash must equal
the previous event's hash, and every hash must be recomputable from its event. A full verification also requires
the stored chain head to match the last event, which catches events deleted from the end. fromSequence and
toSequence verify a window against its own links only, without the head comparison.
valid is the answer. On failure, failure names the sequence, the event ID, and the reason:
sequence-gap, previous-hash-mismatch, hash-mismatch, or head-mismatch. checked counts verified events,
and unchained counts older events recorded before the chain existed, which are never failures. After a prune the
chain starts at a later sequence, and verification starts from there.
Schedule it and alert when valid is false. A chain proves nothing was changed by someone without write access
to both the events and the chain head; it cannot stop someone with full database access from rewriting both, so
also export regularly to independent storage and compare heads.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/audit/verify" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.audit.verify(
credential: CredentialInput,
input: { tenantId: string; fromSequence?: number; toSequence?: number },
): Promise<{
head: { sequence: number; hash: string; updatedAt: number } | null;
valid: boolean;
checked: number;
unchained: number;
first?: number;
last?: number;
lastHash?: string;
failure?: { sequence: number; id: string; reason: AuditChainFailure };
}>Better IAM is created by Sean Filimon
Last updated
assertions
Assertions are short-lived signed tokens that tell another service who is calling: the caller's identity, tenant, roles, groups, and whether they used MFA.
auth
The auth group signs people in and lets them manage their own account security: passwords, one-time codes, passkeys, MFA, sessions, and recovery.