> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dairo.app/llms.txt
> Use this file to discover all available pages before exploring further.

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

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

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl "https://api.dairo.app/v1/audit-logs/export?from=2026-06-01T00:00:00Z&to=2026-06-10T00:00:00Z" \
    -H "Authorization: Bearer $DAIRO_API_KEY"
  ```

  ```ts title="TypeScript" theme={null}
  const exportResult = await dairo.auditLogs.export({
    from: "2026-06-01T00:00:00Z",
    to: "2026-06-10T00:00:00Z",
  });
  console.log(exportResult.rows.length, exportResult.manifest.signature);
  ```

  ```python title="Python" theme={null}
  export = dairo.audit_logs.export(from_="2026-06-01T00:00:00Z", to="2026-06-10T00:00:00Z")
  print(len(export.rows), export.manifest["signature"])
  ```

  ```text title="MCP" theme={null}
  Tool: get_compliance_reports   (scope account:read)
  Args: { "action": "auditExport", "from": "2026-06-01T00:00:00Z", "to": "2026-06-10T00:00:00Z" }
  ```
</CodeGroup>

You get back the rows and a signed manifest:

```json theme={null}
{
  "object": "audit_export",
  "rows": [
    {
      "id": "log_3fa85f64",
      "userId": "user_abc",
      "action": "api_key.created",
      "resourceType": "api_key",
      "resourceId": "key_123",
      "actor": "key_456",
      "ip": "203.0.113.7",
      "chainSeq": 1,
      "prevHash": "",
      "rowHash": "a1b2c3…",
      "createdAt": "2026-06-01T00:00:01Z"
    }
  ],
  "manifest": {
    "type": "dairo.audit.manifest.v1",
    "issuer": "dairo.app",
    "userId": "user_abc",
    "region": "EU",
    "fromChainSeq": 1,
    "toChainSeq": 1,
    "rowCount": 1,
    "headRowHash": "a1b2c3…",
    "exportedAt": "2026-06-12T10:00:00Z",
    "signature": "…",
    "kid": "dairo-ed25519-2026-06",
    "alg": "EdDSA"
  },
  "pagination": {
    "nextCursor": null
  }
}
```

## 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 `rowHash` is 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:

1. Fetch Dairo's public key from the [JWKS](/agents/agent-passport#verify-a-signed-message) by the
   manifest's `kid`.
2. Verify the manifest signature over its committed fields (window bounds,
   `rowCount`, `headRowHash`).
3. Walk `rows` in order: each `rowHash` is `sha256(prevHash || canonical(row))`,
   with the genesis row's `prevHash` empty. Confirm the chain is contiguous by
   `chainSeq` — a gap means a row was deleted.
4. Confirm the final `rowHash` equals the manifest's `headRowHash`.

If all four pass, the slice is exactly what Dairo recorded for that window — no
additions, no deletions, no edits.

<Tip>
  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.
</Tip>

## Next steps

<CardGroup cols={2}>
  <Card title="Audit logs" icon="scroll" href="/compliance/audit-logs">
    The live trail this export is drawn from.
  </Card>

  <Card title="Data erasure" icon="eraser" href="/compliance/erasure-jobs">
    Subject erasure with signed deletion certificates.
  </Card>

  <Card title="Data residency" icon="earth-europe" href="/compliance/residency">
    Where your data lives and the subprocessor list.
  </Card>

  <Card title="API reference" icon="code" href="/api-reference">
    `GET /v1/audit-logs/export`.
  </Card>
</CardGroup>
