
# List audit events

`GET /v1/audit-events` · permission `audit-events:read`

Lists the environment's audit records with cursor pagination, newest first. The `admin` stream holds changes made through the management API, the dashboard and the platform; the `application` stream holds events your application wrote. Every record is part of a hash chain per environment and stream, so the history can be verified independently with an export (`POST /v1/audit-exports`).

The environment always comes from the API key; there is no environment parameter.

## Request

| Parameter | In | Required | Description |
|---|---|---|---|
| `limit` | query | no | Page size, 1 to 100. Default 20. |
| `after` | query | no | Opaque cursor from `list_metadata.after` (older records). Exclusive with `before`. |
| `before` | query | no | Opaque cursor from `list_metadata.before` (newer records). Exclusive with `after`. |
| `stream` | query | no | `admin` (default) or `application`. |
| `from` | query | no | RFC 3339 time; records at or after it. Defaults to the later of the start of the retention window and `to` minus 92 days. |
| `to` | query | no | RFC 3339 time; records before it. Defaults to now. The range from `from` to `to` is at most 92 days. |
| `action` | query | no | Action prefix such as `user.` or `connection.updated`. |
| `category` | query | no | `authentication`, `session`, `token`, `administration`, `provisioning`, `federation`, `authorization`, `data_access`, `security`, `key_management`, `configuration` or `application`. |
| `actor_type` | query | no | Actor type such as `api_key`, `dashboard_user`, `m2m_client`, `agent` or `system`. |
| `actor_id` | query | no | Exact actor id as recorded (`key_…`, `usr_…`, `system:<service>`). |
| `target_type` | query | no | Target type such as `user`, `organization` or `audit_export`. |
| `target_id` | query | no | Exact target id. With `target_type`, both must match the same target. |
| `org_id` | query | no | Records about one organization (`org_…`). |
| `outcome` | query | no | `success`, `failure`, `denied` or `error`. |
| `request_id` | query | no | `Idp-Request-Id` of a request (UUID). |
| `correlation_id` | query | no | `Idp-Correlation-Id` of a workflow (UUID). |
| `agent_only` | query | no | `true` returns only agent actions: `actor_type` `agent` or a non-empty `act_chain`. |
| `client` | query | no | The kind of client that made the call: `mcp` (the management MCP server, `metadata.client` = `mcp/<version>`) or `cli` (the KeenAuth CLI, `cli/<version>`). The client declares this value itself; it is not proof of who acted, the record's actor is. List only: an export has no `client` filter. |

Records are always ordered by `occurred_at` (then `id`), newest first; there is no `order` parameter (`order=desc` is accepted, any other value fails with `validation_failed`). A cursor is only valid with the filters and the environment it was issued for. No total count is returned.

A search that takes longer than 10 seconds stops with `422 search_too_broad`: narrow the filters or the time range, or use an export for large ranges.

## Response

`200 OK`. `data` holds the records on the page; `list_metadata.after` points to older records and `list_metadata.before` to newer ones (`null` when there are none). Empty fields are `null`.

`ip` and `user_agent` are present only when the key has the `audit-events:read_ip` permission, and are filled only for records younger than 13 months. Without the permission the fields are left out entirely (no masked value). `ip_prefix`, `ip_country`, `ip_asn` and `user_agent_family` are always returned. `prev_hash`, `org_prev_hash` and `record_hash` are unpadded base64url SHA-256 values.

```json
{
  "data": [
    {
      "id": "evt_01j9xk3m7q8r2s4t6v8w0y2z4a",
      "stream": "admin",
      "seq": 4182,
      "schema_version": 1,
      "action": "user.blocked",
      "category": "administration",
      "outcome": "success",
      "reason_code": null,
      "occurred_at": "2026-10-01T08:15:42.120Z",
      "received_at": "2026-10-01T08:15:42.131Z",
      "recorded_at": "2026-10-01T08:15:42.140Z",
      "clock_anomaly": false,
      "actor_type": "api_key",
      "actor_id": "key_01j9xk3m7q8r2s4t6v8w0y2z4d",
      "act_chain": null,
      "session_id": null,
      "auth_context": null,
      "elevation_ref": null,
      "org_id": null,
      "targets": [
        {"type": "user", "id": "usr_0a1b2c3d4e5f6g7h8j9k0m1n2p", "org_id": null, "name": null}
      ],
      "application_id": null,
      "ip_prefix": "203.0.113.0/24",
      "ip_country": "DE",
      "ip_asn": 64500,
      "user_agent_family": "keenauth-go",
      "request_id": "0192a6c1-7d3e-7a10-9b2c-3d4e5f607182",
      "correlation_id": null,
      "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
      "producer_service": "directory",
      "policy_ref": "perm:users:write",
      "changes": {"status": {"old": "active", "new": "blocked"}},
      "metadata": null,
      "prev_hash": "n4bQgYhMfWWaL-qgxVrQFaO_TxsrC4Is0V1sFbDwCgg",
      "org_prev_hash": null,
      "record_hash": "LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564"
    }
  ],
  "list_metadata": {
    "before": null,
    "after": "<opaque cursor>"
  }
}
```

## Errors

Every error is an `application/problem+json` body with a stable `code`.

| HTTP | `code` | When |
|---|---|---|
| 400 | `invalid_cursor` | The cursor is malformed or was not issued for this request. |
| 400 | `invalid_filter` | The audit service rejected a filter, for example a range longer than 92 days. |
| 400 | `range_outside_retention` | `from` or `to` is outside the audit retention window of your plan. |
| 422 | `validation_failed` | A query parameter is invalid (`limit` out of range, bad timestamp, `to` not after `from`, unknown `stream`, `category` or `outcome`, malformed id, repeated parameter, `order` other than `desc`). |
| 422 | `search_too_broad` | The search took longer than 10 seconds; narrow it. |
| 401 | `unauthenticated` | The API key is missing, invalid or revoked. |
| 403 | `insufficient_permissions` | The key lacks `audit-events:read`. |
| 429 | `rate_limited` | The rate limit was exceeded; honor `Retry-After`. |
| 503 | `service_unavailable` | A dependency is temporarily unavailable; retry later. |

## Example

```bash
curl "https://api.eu.keenauth.com/v1/audit-events?action=user.&from=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY"
```

```bash
keenauth audit-events list --action user. --from 2026-10-01T00:00:00Z
```

## Personal data

This endpoint carries personal data: audit events. [Personal data in the KeenAuth APIs](/docs/policies/personal-data) describes what is stored, how long it is kept and how it is deleted. Do not send special categories of personal data (health, biometric data, religion, political opinions, sexual orientation) in any field.
