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

# Audit Log Export

> Queue the full audit ledger to a downloadable file with the async export endpoint, poll for a signed URL, understand the export-row schema and redaction, and load the rows into a BI tool.

# Audit Log Export

This is the operator guide for exporting the audit ledger to a file. It covers the async endpoint contract, the shape of the exported rows, how authorization and redaction work, and how to consume the result. For what the ledger captures and the live filters, see [Audit Log](/guides/audit-log).

<Warning>
  Export and archival posture is a set of tenant-owned controls. It does not
  by itself satisfy any particular framework. This page is not legal advice;
  check your own retention policy and counsel.
</Warning>

## What the export ships

There are two export paths, and they ship different row shapes. Read this before parsing a file:

* **Synchronous quick export** (`GET /settings/audit-logs/export`) — curated six-column shape: `timestamp`, `user`, `action`, `resource`, `resource_id`, `ip_address`. Internal hash-chain columns, organization/tenant ids, raw user ids, and user-agent are deliberately projected out, so the file is what a reviewer actually needs and nothing more. Capped at 10,000 rows. The response schema validates each row explicitly rather than treating it as a free-form object.
* **Async export** (this page) — streams the row set the underlying ledger keeps, in one of three formats: `json` (one JSON object per entry, all ledger fields including `id`, `organization_id`, `tenant_id`, `user_id`, `action`, `resource`, `resource_id`, `details`, `ip_address`, `user_agent`, `current_hash`, `created_at`), `csv` (same fields, RFC 4180 escaped), or `scim` (RFC 8417 Security Event Tokens, the envelope SIEM pipelines ingest directly). No row cap below the 1M-row hard ceiling.

Pick the format by consumer: `json` for a BI/SQL lifter, `csv` for spreadsheet review, `scim` when the downstream is a log drain that speaks Security Event Tokens. Both `json` and `scim` arrive as one JSON array; `csv` arrives as a header plus escaped lines.

## Async export endpoint

Use the async path when the ledger exceeds the quick export's 10,000-row cap, or when the download is part of a compliance pull that must not truncate.

`POST /api/v1/settings/audit-logs/export-async` accepts a JSON body:

| Field | Values | Default |
| - | - | - |
| `format` | `csv` (default), `json`, or `scim` (SCIM Security Event Tokens) | `csv` |
| `date_range` | `24h`, `7d`, `30d`, `90d`, `all` | `30d` |

It returns `202` immediately with the new job's id:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/settings/audit-logs/export-async" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"format": "json", "date_range": "90d"}'
```

```json theme={null}
{
  "data": { "job_id": "auditExportJob_", "status": "pending" },
  "meta": { "request_id": "req_...", "timestamp": "2026-10-01T00:00:00.000Z" }
}
```

Poll `GET /api/v1/settings/audit-logs/export-jobs/{job_id}`. The job moves `pending → running → complete`, and on `complete` the response's `gcs_url` carries the signed download URL. Download with a plain `GET` — no API key needed on the URL itself (treat it as a secret; do not paste it into chat or tickets). `GET /api/v1/settings/audit-logs/export-jobs` lists your org's recent jobs newest-first, cursor-paginated by `createdAt` so you can page back through past runs.

## Authorization and redaction

Export endpoints are owner/admin only — the same gate the dashboard's Audit Log page enforces. API-key authentication uses a key created by an owner or admin; dashboard Bearer tokens pass when the session role clears the gate.

The sync export's curated projection is itself a redaction control: the internal tamper-checksum columns, the raw `user_id`, and `user_agent` never leave the platform on that path. For the async export, the raw ledger fields listed above are shipped intentionally — a DSAR or an audit reviewer needs the full record, and the file lands behind a signed URL only the requester receives. Scope your download handling accordingly: hold the URL as a secret, and delete the downloaded copy under your own archival policy (see [Immutable Archival Export](/compliance/archival-export) for the retention/retrieval split).

Jobs are scoped to the requesting organization: `GET` a job id that another organization queued and you get a 404, not the job. Re-queuing an export does not leak prior generations' URLs — each job mints its own signed URL.

## Consuming the export — worked example

The most common downstream is a BI or SQL tool. The `json` format is the cleanest lifter — one object per entry, every field named and primitive except `details` (a nested JSON object for before/after payloads).

```bash theme={null}
# 1. Enqueue, capturing the job id
JOB_ID=$(curl -s -X POST "https://api.orbit.devotel.io/api/v1/settings/audit-logs/export-async" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"format": "json", "date_range": "90d"}' | jq -r '.data.job_id')

# 2. Poll until the signed URL appears
URL=$(curl -s "https://api.orbit.devotel.io/api/v1/settings/audit-logs/export-jobs/$JOB_ID" \
  -H "X-API-Key: dv_live_sk_..." | jq -r '.data.gcs_url // empty')
[ -n "$URL" ] || { echo "not ready yet — poll again"; exit 1; }

# 3. Download and load
curl -s "$URL" -o audit-export.json
python - <<'PY'
import json, collections
rows = json.load(open('audit-export.json'))
by_action = collections.Counter(r['action'] for r in rows)
for action, n in by_action.most_common(10):
    print(f"{n:7d}  {action}")
PY
```

The same shape drops directly into Pandas (`pd.read_json('audit-export.json')` → one row per ledger entry, `details` explodable with `df.join(pd.json_normalize(df['details']))`), DuckDB (`SELECT * FROM read_json_auto('audit-export.json')`), or a Google Sheet import after converting to CSV. For a pull into a SIEM pipeline, request `format: "scim"` once and hand the file to your log drain's file-watcher — each array element is a complete Security Event Token the drain can ingest without transformation.

When you need exactly the on-screen filter set narrowed to a small window rather than the whole ledger, prefer the synchronous quick export endpoint instead — its curated six columns tend to be what a reviewer actually wants from a focused pull.

## Limits

* **Rate** — enqueue and quick-export endpoints are limited to 5 requests per minute per organization. Rapid duplicates return `429`; a queued job is cheap to leave pending, so queue once and poll.
* **Row ceiling** — the async path hard-caps at 1,000,000 rows (many years of typical ledger volume). The quick export caps at 10,000 rows and flags truncation rather than silently handing you a partial file (`truncated: true` in the envelope, plus an `X-Export-Truncated: true` response header).
* **Signed URL lifetime** — the download URL is valid for 24 hours. Polls that arrive after expiry report the job as `expired` with no URL; re-queue to mint a fresh one.
* **Date window** — pick `24h`/`7d`/`30d`/`90d` for bounded reviews, `all` only for a full-ledger pull (a GDPR DSAR or an annual evidence sweep). The smaller the window, the faster the job completes.
* **Large exports** — page by narrowing `date_range` and splitting into per-week or per-month jobs rather than one `all` pull. Each job page-fetches the ledger in 1,000-row batches server-side, so splitting a huge range across N jobs costs the platform no more than one big job and gives you N smaller, resumable downloads.
