BetterIAM
Server API

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.

Methods3
Serveriam.api.audit
Clientclient.audit
HTTPPOST /api/iam/audit/*
MethodWhat it doesAccess
exportReturns a page of the tenant's audit events in chain order as JSON Lines, ready to archive.Credential
listSearches the tenant's audit events, newest first, by actor, action, resource, outcome, and time range.Credential
verifyChecks 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.

POST/api/iam/audit/export
client.audit.export()Credential

Used inAudit chain,Operations recipes

  • Permission: iam:audit:read on the tenant.
  • Audited as: iam:audit:read.
  • Errors: INVALID_INPUT when fromSequence is below 1 or limit is 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;
}
Input

Prop

Type

Returns

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>"
}'
Signature
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.

POST/api/iam/audit/list
client.audit.list()Credential

Used inEvents and audit

  • Permission: iam:audit:read on the tenant.
  • Audited as: iam:audit:read.
  • Errors: INVALID_INPUT when outcome is not allow or deny, or limit (1 to 1000), offset, from, or to is 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,
});
Input

Prop

Type

Returns

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>"
}'
Signature
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.

POST/api/iam/audit/verify
client.audit.verify()Credential

Used inEnterprise onboarding,Audit chain,Operations recipes

  • Permission: iam:audit:read on the tenant.
  • Audited as: iam:audit:read.
  • Errors: INVALID_INPUT when fromSequence or toSequence is 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.

Input

Prop

Type

Returns

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>"
}'
Signature
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 };
}>

Was this page helpful?

Better IAM is created by Sean Filimon

Last updated

On this page