Tamper-evident audit export
Export your audit trail with cryptographic proof it wasn't edited after the fact.
Hand an auditor your audit trail with proof it wasn't edited first. The export returns your audit rows as a hash-chained slice plus a signed manifest, so anyone can re-check the chain and verify the signature offline to confirm nothing was added, removed, or altered.
Reach for it when a regulator, customer, or security review needs evidence rather
than a screenshot. The export uses the account:read scope, the same as reading
the live trail, and is refused (403) to a key restricted to specific inboxes.
Every export is signed: if Dairo cannot sign it, the call fails with a 500
rather than handing you an unsigned manifest.
Export a window
Narrow the window with from / to (RFC 3339). With neither set, the export covers
the last 30 days rather than your whole history; supplying either bound requests an
explicit window. A page holds up to 1000 rows; when more remain, the response carries
a pagination.nextCursor that you pass back as after to fetch the next page. Each
page is its own contiguous, independently verifiable chain segment, so paging never
breaks the proof.
You get back the rows and a signed manifest:
How the proof works
Two things make the export tamper-evident, and you can check both yourself:
- A hash chain over the rows. Each row's
rowHashis computed from the previous row's hash plus its own contents, so the rows form a chain. Remove or edit one and every hash after it stops matching. - A signed manifest. The manifest commits to the window, the row count, and the final row hash, then signs that with Dairo's key. The signature only checks out if those committed values are exactly what was exported.
Verify an export offline
Anyone — you, an auditor, a customer's security team — can confirm an export is intact without calling Dairo, using only the published verification key:
- Fetch Dairo's public key from the JWKS by the
manifest's
kid. - Verify the manifest signature over its committed fields (window bounds,
rowCount,headRowHash). - Walk
rowsin order: eachrowHashissha256(prevHash || canonical(row)), with the genesis row'sprevHashempty. Confirm the chain is contiguous bychainSeq— a gap means a row was deleted. - Confirm the final
rowHashequals the manifest'sheadRowHash.
If all four pass, the slice is exactly what Dairo recorded for that window — no additions, no deletions, no edits.
When an export spans more than one page, follow pagination.nextCursor with after
to drain the full range. Store each manifest alongside its rows so the proof stays
attached to the data.