SettleMint
ConceptsRecords & evidence

Audit Trail

An audit trail lets you show a regulator or an auditor who did what, under which role, without relying on the requester's own account of the request.

Audit trail: two records, two different proofs.On chain, a write is provable only as far as which smart account signed it. Off chain, the platform's audit trail binds that same request to the human actor, the role they held at the time, and the route they called, written as one record per audited request. The trail proves who acted under what role, on which route, with what outcome. It does not prove which party a delegated actor was standing in for.Audit trail: two records, two different proofs.The same figure as the desktop variant, drawn portrait for phone widths. A chain signature proves which smart account signed. The off-chain audit trail writes one record per audited request, binding it to the human actor, their role at the time, and the route. Below the record sit its two halves: what it proves, and what it does not prove, which is the party a delegated actor was standing in for.

The trail binds a request to the actor and role that made it, not to which party a delegated actor was standing in for. The two are different guarantees, and only the first one is recorded.

The trail is written by request middleware from each route's declared audit disposition, not by whoever wrote the feature remembering to log it.

A mutation that cannot be recorded does not run, with one exception: authentication fails open, so a trail outage cannot lock every operator out. A sensitive read that cannot be recorded is still served, with the failure logged loudly.

Under a delegated authority the trail names the operator's own session, not the party they were acting for.

Why the chain cannot answer who acted

A signature on a blockchain transaction proves which smart account sent it. It says nothing about which person sat behind that account, which role they held, or whether that step was theirs to take. A regulator asking who approved a compliance override, or an auditor confirming that only cleared staff opened a customer's file, is asking about a person and a permission, and a chain answers about a wallet.

Some of what the platform does never reaches the chain at all: a denied request, a rejected submission, a configuration edit, a sensitive read. None of those produce a transaction, so a chain-only investigation finds nothing where the concerning event is precisely that something happened and nothing was written.

The audit trail closes that gap. It binds every state-changing or sensitive request to the human actor, the role they held at that moment, and the outcome, whether or not the request ever reached the chain. Coverage follows the route's own declared audit disposition rather than a developer remembering to log it, described in full below. The trail is read back through the same part of the platform that already produces what a regulator must be told, which is why it is one of the reporting obligation export's own report kinds, covered later on this page.

Why this trail covers the whole platform, not one asset's life

Asset Lifecycle indexes a different trail: every stage of one instrument's life, from design through issuance, servicing, and retirement, against that same asset's own indexed record, which is what makes that instrument's whole life auditable from one place. That trail answers whether an act on that instrument was authorized and left a record.

The audit trail answers a wider question. It covers every state-changing or sensitive request across the platform, regardless of whether an asset is even involved. Signing in, resetting a password, creating an API key, and enabling two-factor authentication all get an entry, described in the section below, alongside every transfer, coupon run, and trade that touches an asset. An entry names the actor and the role they held; it names an asset only when the request itself acted on one.

What the trail covers, and what it leaves out

Coverage, mutability, exportability, and retention decide which evidence you can retrieve later, not just what gets written today. Application-level write restrictions do not make the database itself a write-once archive.

What is logged: every state-changing request, and every read whose occurrence itself matters, tied to the person who made it, the role they held at that moment, their tenant, and the outcome. Ordinary reads are not logged. Logging every read would make the trail itself a privacy problem.

What is immutable: once an entry opens, the interface the platform code writes through offers exactly two operations, close it as a success or close it as a failure. That interface has no delete or edit path, a discipline enforced in application code, not a database-level guarantee such as write-once storage.

What is exportable: three read shapes plus a reporting-obligation export, described in full in How you read the trail below.

What the retention is: nothing is configured. An entry has no expiry, and no scheduled job deletes one, so entries persist until someone decides otherwise.

Two writes bracket every request

The audit trail writes an entry twice for every audited request: once before the handler runs, recording that the attempt happened, and once after, closing the entry with the outcome. A request that dies between the two writes has already left the first one, so no attempt vanishes without a trace.

The first write lands before the handler runs; the second closes the entry with the outcome.An audited request produces two writes. Before the handler runs, an entry opens as pending. Then the handler runs, and a second write closes the entry with the outcome, success or failure. If the handler never finishes, the entry stays pending, and that stuck pending entry is itself evidence of the attempt: the platform indexes exactly that case so it can be found.The first write lands before the handler runs; the second closes the entry with the outcome.The same figure as the desktop variant, drawn portrait for phone widths. An entry opens as pending before the handler runs, the handler runs, and a second write closes the entry with the outcome. If the handler never finishes, the entry stays pending, and that stuck pending entry is indexed evidence of the attempt.

The first write records the attempt before the handler runs; the second closes the entry with the outcome. A crash between the two leaves the entry pending, and a stuck pending entry is itself evidence.

An entry still pending well after its request should have finished is the signal that the completion write failed, not that nothing happened. The platform indexes exactly that case so stuck entries can be found rather than discovered by accident during an investigation.

The two kinds of audited requests fail differently on purpose when the opening write itself cannot land:

Mutation

Refused outright, before the handler ever runs, so no partial state exists on either side. An unrecorded state change is the exact gap the trail exists to close, so the platform blocks the attempt rather than let it happen invisibly. The refusal costs no availability the mutation had anyway: the trail lives in the same store the mutation was about to write to. The caller gets the platform's standard INTERNAL_SERVER_ERROR; its reference entry says to retry once and contact support if it persists.

Sensitive read

Served anyway. The read changes nothing, so refusing it would trade real availability for a weaker claim. The platform logs the audit-write failure for operators to investigate.

Blocking an unrecorded mutation is the rule for every route the request middleware covers. Authentication is the one exception: signing in, signing out, signing up, resetting or changing a password, creating or deleting an API key, and enabling, disabling or verifying two-factor all write to the same trail. They go through separate lifecycle hooks rather than the middleware, since those requests run before a session exists for the middleware to attach to. Locking every operator out of the platform because the trail is briefly down would be worse than the gap it is meant to close. So these writes fail open: the attempt still succeeds, and a missed write is logged rather than blocking it.

One record, written by declaration

One internal record holds every audited entry: the actor, the role they held at that moment, their tenant, the target, the route, the outcome, and the timing. Request middleware writes it, not the code that handles the request, so coverage follows each route's declared audit disposition rather than anyone's diligence.

Every HTTP API route declares that disposition: mutation, sensitive read, or none. A route with no explicit declaration defaults from its access declaration, so a state-changing route is covered from the moment it is written rather than when someone remembers to instrument it. A coverage guard rejects a write route that opts out without a written exemption.

The entry records the request's target, never its body. It stores the route and the resolved path it acted on, plus the query string with credential and personal-data parameters such as passwords, PIN codes, one-time codes, and recovery codes redacted, bounded to 1,024 bytes. Request bodies are never stored. Redaction is by parameter name, so an ordinary filter value such as an email can still appear in the query string; keep personal data out of query parameters. A consolidated transition entry also carries the requested destination state as its reason. A failed transition records the state it was asked to reach, not one it reached. The entry also holds no database link to the organisation, user, or participant it names, the compliance subject recorded for that request, so it outlives all of them.

The entry's participant field is documented as the compliance subject the actor was acting as, but the write path fills it only from the session's own participant, and nothing updates it afterward. A write made under a delegated authority, the arrangement where one party acts for another, therefore names the operator, not the party they acted for. A participant requested with the X-Participant header appears only in the target as participant=<id>. The row is recorded before that selection is authorized, so it is a request, not proof of whom the actor acted for. The session also holds a separate marker for admin support sessions, read elsewhere to block certain sign-offs while impersonating, but that marker is not written onto the audit entry at all.

How you read the trail

The shape of your question decides which read to use. Self-service, the Console's audit trail page shows a member of an organisation the last 30 days of its own operations. It is gated no tighter than the API behind it: any member of the active organisation may open it, because a compliance officer who cannot approve anything still needs to see what happened.

The API answers three other shapes of question, detailed in full in the API reference. It covers a time window over your own tenant's trail, one entry by its id, and every entry that touched a given resource across every actor and route. That third shape is usually where an investigation starts, because the question is "what happened to this thing", not "what did this person do". A fourth shape, gated to platform administrators, spans every tenant plus the entries that belong to no tenant at all.

Every one of those reads shares the same cap: 200 entries per call, with no cursor to ask for more. A window that holds more than that is silently truncated, and the response's total count reflects only what came back, not what the window actually holds. For a window too large to trust to one capped call, the trail is also one of the platform's own report kinds. A reporting obligation can carry it in its report set, and that export reads the same table without the row limit.

Two safety details matter for an integrator. Reading the trail is itself a sensitive read and gets its own entry, so the trail records its own readers. And a lookup of another tenant's entry returns not found rather than forbidden, because a forbidden response would confirm the identifier exists, which is itself a disclosure across tenants. A not-found on your own tenant's entry can also mean the window missed it, so narrow the window before assuming the id is wrong.

What you could and could not show a regulator

Self-service from the Console, you could show a regulator your organisation's operations today: the actor, the role they held at that moment, the route, and the outcome. You could not show which party a delegated actor was standing in for. And going back before the trail existed, you could show nothing at all: coverage starts the day the audit route trait went live, not on whatever earlier date a request happens to predate it.

That limit follows from the write path already described. The evidence for who a delegated actor was acting for lives elsewhere: the ledger names the party's account as the sender of the transaction, and the party's own role grant registry names who could act for it at the time. Delegated Authority covers both.

Two claims are tempting and wrong. The missing update and delete path is an application-code constraint, not a database guarantee such as write-once storage, so do not claim database-enforced immutability. And do not claim a retention policy: no retention period is configured, so if a regulator asks how long an entry is kept, the honest answer is indefinitely, by omission rather than by policy.

Be precise about the window rather than the access. The Console gives an organisation member the recent trail without an engineer, which makes it self-service evidence, but the screen shows the last 30 days. A control that needs a window longer than one capped API call can safely return should get it from a reporting obligation's export, not from the screen or a truncated call.

Frequently asked questions

Does an audit entry show what a request changed?

No. The entry captures the request's target, with sensitive query parameters redacted, and never its body, for the privacy reason given above: request bodies routinely carry personal data the trail is not built to hold. For before and after values, the trail is the wrong record regardless: it proves who acted on what, not what the data looked like.

Are denied and failed requests recorded?

Yes. The opening write lands before the handler runs, so an attempt is recorded whether it later succeeds, fails, or is refused, and the closing write stores the HTTP status the caller received. A denial or a rejected submission never reaches the chain at all, which is exactly why the off-chain trail records it. A caller with no session is recorded too, with the actor marked as anonymous.

How do I tie an audit entry to logs and the transaction it produced?

Through the request identifier. Every entry stores the id of the request that produced it, and the same identifier appears in the platform's structured logs and traces for that call. For a write that reached the chain, it is also the join key back to the resulting transaction, so one identifier connects the trail entry, the logs, and the on-chain effect.

What happens to an entry when the account it names is deleted?

Nothing. The entry holds no database link to the account it names, for the reason given above, so deleting an account never cascades into the evidence that the account once acted.

  • Delegated Authority: The umbrella model whose attribution limit this page inherits: the trail's participant field holds the session participant, not the assumed one.
  • Participant: The compliance subject a delegated actor steps into.
  • Role Grant: The on-chain permission an audit entry's role-at-time field records. The trail observes its effect on a request; it does not itself grant or check one.
  • Reconciliation: A related record for a different question, one names who acted, the other confirms whether ledgers agree.
  • Reporting Obligation: The versioned filing and export mechanism that can carry the trail in its own report set when a window is too large for the capped read API.

On this page