# Export the audit log

Download the audit log as CSV or NDJSON, or pull it into a SIEM on a schedule with an API token.

Source: https://opafra.com/docs/governance/export-the-audit-log

---

Two ways out: a download from the audit log page for a one-off request, and an API a
SIEM can poll for everything new.

## Before you begin

- You need the **Admin** role or above. Exporting is reading the whole log, so it needs
  the same role as reading it.
- For the API, create an [API token](/docs/reference/api#authentication) from an Admin
  account. Keep that account's role no wider than it has to be: the token carries it.

## 1. Download a file from the page

1. Open **Audit log**.
2. Narrow it down with the search, the filters and the dates. Leave them empty to take
   everything.
3. Choose **Export**, then **CSV** for a spreadsheet or **NDJSON** for a log tool.

The file holds **every entry that matches the filters**, not only the rows loaded on the
page. The category chips above the timeline are not part of the export; use the action
and resource filters for that.

> **Note**
>
> Exporting is itself recorded, as `audit_log.export`, with the format, the filters and the
> number of entries. A log that can be copied out unseen is half an audit log.

## 2. Or pull it into a SIEM

Call the events endpoint with your token. It returns the oldest entries first, a page at
a time:

```bash title="first page"
curl -H "Authorization: Bearer opf_..." \
  "https://your-opafra-host/api/audit-logs/events?limit=500"
```

```json title="response"
{
  "data": [ { "id": "…", "timestamp": "2026-09-22T08:14:03.000Z", "evt.name": "auth.login.failed", "…": "…" } ],
  "next_cursor": "MjAyNi0wOS0yMlQwODoxNDowMy4wMDBa…",
  "has_more": true
}
```

Pass `next_cursor` back as `cursor` to get the next page. Keep calling while `has_more`
is `true`.

```bash title="next page"
curl -H "Authorization: Bearer opf_..." \
  "https://your-opafra-host/api/audit-logs/events?limit=500&cursor=MjAyNi0wOS0y..."
```

When `has_more` is `false` you are up to date. **Store the last `next_cursor`.** It is
returned even on the last page, so the next scheduled run asks for exactly what arrived
since and nothing is fetched twice.

A page holds up to 1000 entries. The events endpoint accepts the same filters as the
page: `from`, `to`, `action`, `resource`, `outcome`, `actorEmail`, `ip` and `q`.

> **Important**
>
> Use the cursor, not dates, to resume. New entries keep arriving while you page, and a
> cursor is the only position that does not shift under you.

## What each entry contains

One flat object per entry, in the API and in both file formats. The names follow
Datadog's standard attributes where one exists, so most log tools index them without a
custom parser.

| Field | What it is |
|---|---|
| `id` | Stable id. Use it to drop a duplicate if you fetch a page twice |
| `timestamp` | When it happened, in UTC |
| `evt.name` | The action, such as `auth.login.failed` or `execution.start` |
| `evt.outcome` | `success` or `failure`. Empty on entries older than September 2026 |
| `actor_type` | `user`, `api_token`, `system`, or `anonymous` for a sign-in that failed |
| `usr.id`, `usr.email` | Who acted. Empty when nobody had signed in |
| `attempted_email` | The address typed on a failed sign-in. Never the actor |
| `resource_type`, `resource_id` | What the action touched |
| `network.client.ip` | Where the request came from |
| `http.useragent` | The browser or client that sent it |
| `http.request_id` | Matches the request in Opafra's own logs, for support |
| `summary` | One line for a person to read |
| `metadata` | Detail specific to the action. Never a secret value |

## Verify it worked

Open **Audit log** and filter on the action `audit_log.export`. Your download appears with
the number of entries it contained. Compare that number with the rows in your file.

## Next steps

- [Audit log](/docs/governance/audit-log) for what is recorded and who can read it
- [API](/docs/reference/api) for tokens and the other endpoints
