# audit (/docs/reference/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](/docs/guides/events/audit-chain).

## What an audit event holds [#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`](#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](/docs/reference/cli#audit-verify) run the same checks and export with deployment access, and record
nothing.

| Method              | What it does                                                                                                  | Access     |
| ------------------- | ------------------------------------------------------------------------------------------------------------- | ---------- |
| [`export`](#export) | Returns a page of the tenant's audit events in chain order as JSON Lines, ready to archive.                   | Credential |
| [`list`](#list)     | Searches the tenant's audit events, newest first, by actor, action, resource, outcome, and time range.        | Credential |
| [`verify`](#verify) | Checks the tenant's audit hash chain and reports whether any stored event was altered, reordered, or removed. | Credential |

## export [#export]

Returns a page of the tenant's audit events in chain order as JSON Lines, ready to archive.

**HTTP:** `POST /api/iam/audit/export` (requires a credential) · **Browser client:** `client.audit.export()`

* **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](/docs/reference/api#pruneaudit) keeps the full history verifiable.

```ts
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;
}
```

```ts title="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 [#list]

Searches the tenant's audit events, newest first, by actor, action, resource, outcome, and time range.

**HTTP:** `POST /api/iam/audit/list` (requires a credential) · **Browser client:** `client.audit.list()`

* **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".

```ts
const denials = await iam.api.audit.list(credential, {
  tenantId,
  outcome: 'deny',
  from: Date.now() - 60 * 60 * 1000,
});
```

```ts title="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 [#verify]

Checks the tenant's audit hash chain and reports whether any stored event was altered, reordered, or removed.

**HTTP:** `POST /api/iam/audit/verify` (requires a credential) · **Browser client:** `client.audit.verify()`

* **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.

```ts title="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 };
}>
```
