Audit log

Read and export the audit trail of administrative actions in your organization.

The organization's audit log: who did what, when, from where, on what — and, for actions taken through this API, with which token. It is the same log the portal shows, and it is append-only: no endpoint edits or deletes an entry.

EndpointScopeToken owner's role
GET /auditaudit:readAdmin
GET /audit/exportaudit:readAdmin

Both routes need an owner who is currently an organization Admin; a Member's token gets 403 insufficient_scope. An organization sees its own log and nothing else.

What is recorded

RecordedExamples
Writes on this APIproject_create, project_rename, project_archived, key_create, key_rotate, member_role_update, invite_revoke, org_settings_update, … — the same action names the portal records. The table below maps each call to its action.
Token lifecyclepat_create, pat_revoke (with the token id, name, scopes and expiry).
Secret replayskey_create_replayed, key_rotate_replayed — an idempotent replay delivered a secret a second time.
Bulk exportsaudit_export, usage_export, with row_count and format.
Portal actionsThe same entries the portal's activity log shows, without the token fields.

Which action each call records

The action names are the portal's, so some differ from the API verbs:

API callaction
PATCH /orgorg_settings_update
PATCH /members/{id} — rolemember_role_update
PATCH /members/{id} — namemember_update
POST /members/{id}/disable, POST /members/{id}/enablemember_update (metadata.is_active is false or true)
PUT /members/{id}/projectsmember_projects_update
DELETE /members/{id}member_remove
DELETE /members/{id}/sessionsmember_sessions_revoke
POST /invitesinvite_create
DELETE /invites/{id}invite_revoke
POST /projectsproject_create
PATCH /projects/{id} — name or descriptionproject_rename
POST /projects/{id}/disableproject_archived
POST /projects/{id}/enableproject_unarchive
DELETE /projects/{id}project_purged
POST /projects/{id}/keyskey_create
PATCH /keys/{id} — namekey_rename
PATCH /keys/{id} — ip_allowlistkey_ip_allowlist_update
POST /keys/{id}/disablekey_deactivate
POST /keys/{id}/enablekey_activate
POST /keys/{id}/rotatekey_rotate
POST /keys/{id}/rotate/cancelkey_rotate_cancel
DELETE /keys/{id}key_revoke

A project's guardrails, and a key's allowed_models, rpm_limit, expires_at and description, are not yet recorded as separate entries when they change.

Ordinary reads are not individually audited — an Admin reading their own organization's data is not a privacy event. Exports are the exception, because an export is what a leak looks like when it is one.

The audit entry

FieldTypeNotes
idstringOpaque id.
created_atstringWhen, ISO-8601 UTC.
actorobjectWho: { "user_id", "email" }. For API calls, the token's owner.
actionstringWhat, e.g. key_rotate.
resource_type, resource_idstring | nullOn what, e.g. api_key and its id.
metadataobjectAction-specific detail (for example row_count on an export).
ip_addressstring | nullFrom where.

Which token acted. An entry written by a call made with a personal access token carries the token in metadata, next to the action's own detail:

metadata keyValue
credential"pat"
pat_idThe token's id.
pat_nameThe token's name.
scopeThe scope the call used, e.g. keys:write.

Portal actions have none of these keys.

{
  "id": "0b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
  "created_at": "2026-09-24T10:05:31Z",
  "actor": { "user_id": "8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968", "email": "[email protected]" },
  "action": "key_rotate",
  "resource_type": "api_key",
  "resource_id": "c41d8e2a-7b90-4f3c-a6e5-2d1f0b9c8a74",
  "metadata": {
    "grace_expires_at": "2026-09-24T12:05:31Z",
    "credential": "pat",
    "pat_id": "d9e8f7a6-b5c4-4d3e-9f2a-1b0c9d8e7f6a",
    "pat_name": "CI · payments-deploy",
    "scope": "keys:write"
  },
  "ip_address": "203.0.113.24"
}

List audit entries

GET /api/v1/mgmt/audit

Newest first, cursor-paginated.

Query parameterTypeNotes
actorstringThe actor's user id, or their email address (case-insensitive).
actionstringExact action name, e.g. key_revoke.
fromstringRFC 3339, inclusive.
tostringRFC 3339, exclusive. Must be after from.
limit, cursorPagination.

Any other parameter returns 422 unsupported_filter; a malformed actor, from or to is 400 invalid_request with param naming it.

curl -G https://portal.<domain>/api/v1/mgmt/audit \
  -H "Authorization: Bearer $TF_PAT" \
  -d [email protected] \
  -d from=2026-09-23T00:00:00Z \
  -d limit=200

Export the audit log

GET /api/v1/mgmt/audit/export

Streams every matching entry, newest first, in one response — for SIEM ingestion or an evidence request.

Query parameterTypeRequiredNotes
formatstringnojsonl (default; one audit entry per line, application/x-ndjson) or csv.
actor, action, from, tonoAs for GET /audit.

The CSV columns are id, created_at, actor_user_id, actor_email, action, resource_type, resource_id, ip_address and metadata (the metadata object as JSON).

At most 10,000 entries per call. A filter that matches more is refused with 422 export_too_large before anything streams — narrow from/to and export in windows, or page through GET /audit. Every completed export is itself recorded as audit_export with the row_count.

curl -G https://portal.<domain>/api/v1/mgmt/audit/export \
  -H "Authorization: Bearer $TF_PAT" \
  -d format=jsonl \
  -d from=2026-09-01T00:00:00Z \
  -d to=2026-10-01T00:00:00Z \
  -o audit-2026-09.jsonl

Tip

For an access review, export a period and group by metadata.pat_id: you get every token that acted, whose it is, which scopes it used, and when.

Errors specific to the audit log

StatusCodeWhen
400invalid_request (param set)actor neither a user id nor an email; from/to not RFC 3339; from not before to; format not jsonl/csv.
403insufficient_scopeToken lacks audit:read, or the owner is not currently an Admin.
422unsupported_filterAn unknown query parameter.
422export_too_largeThe export matches more than 10,000 entries.