Audit Logs
GET /orgs/{orgId}/api/v1/admin/audit-logs
GET /orgs/{orgId}/api/v1/admin/audit-logs/{logId}
GET /orgs/{orgId}/api/v1/admin/audit-logs/stats
GET /orgs/{orgId}/api/v1/admin/audit-logs/actions
GET /orgs/{orgId}/api/v1/admin/audit-logs/export
GET /orgs/{orgId}/api/v1/admin/audit-logs/retention
PUT /orgs/{orgId}/api/v1/admin/audit-logs/retention
The audit log provides an immutable, tamper-evident record of all actions performed within an organization — by users, administrators, agents, and the LumoAuth platform itself.
Authentication
All audit log endpoints require an admin API key or a Bearer token with admin:audit:read (retention update: admin:settings:write).
Each endpoint needs the credential's scope and a permission. A session or Bearer caller must
hold the permission; for an API key its creator must still hold it. A missing permission
returns 403 forbidden with details.required_permissions. See
Required permissions.
| Method | Endpoint | Permission | API-key / token scope |
|---|---|---|---|
| GET | /admin/audit-logs | audit.view | admin:audit:read |
| GET | /admin/audit-logs/{logId} | audit.view | admin:audit:read |
| GET | /admin/audit-logs/stats | audit.view | admin:audit:read |
| GET | /admin/audit-logs/actions | audit.view | admin:audit:read |
| GET | /admin/audit-logs/retention | audit.view | admin:audit:read |
| PUT PATCH | /admin/audit-logs/retention | settings.manage | admin:settings:write |
| GET | /admin/audit-logs/export | audit.export | admin:audit:read |
Audit Log Entry Object
A single-entry (GET /audit-logs/{logId}) response returns the full object below; list items expose the same fields minus the detail-only ones (eventTime, durationMs, initiatedBy, clientId, geoLocation, sessionId, requestId, statusReason, oldValue, newValue, context).
{
"id": 4244,
"createdAt": "2026-03-01T08:00:00Z",
"eventType": "user.login",
"action": "login",
"severity": "info",
"message": null,
"actorId": "019d6190-9bba-7b5f-a0a5-07a6d10184d2",
"actorName": "Alice",
"actorType": "user",
"authMethod": "session",
"targetType": "user",
"targetId": "019d6190-9bba-7b5f-a0a5-07a6d10184d2",
"targetName": "Alice",
"ipAddress": "203.0.113.1",
"userAgent": "Mozilla/5.0 ...",
"status": "success",
"statusReason": null,
"clientId": "my-app",
"sessionId": "859d3386515a369646c54e0675e6500c",
"requestId": "2d92dd89-1b75-4e30-83ee-bb8fc05f574c",
"initiatedBy": "alice@example.com",
"eventTime": null,
"durationMs": null,
"geoLocation": null,
"oldValue": null,
"newValue": null,
"context": { "mfaMethod": "totp" }
}
The entry uses status ("success" / "failure") — there is no result field — and actorName rather than actorEmail. Numeric id, no orgId, and structured detail lives in context / oldValue / newValue rather than a metadata object.
List Audit Logs
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs?startDate=2026-03-01T00:00:00Z&limit=50" \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs?startDate=2026-03-01T00:00:00Z&limit=50", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs?startDate=2026-03-01T00:00:00Z&limit=50",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs?startDate=2026-03-01T00:00:00Z&limit=50", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Query Parameters
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (default: 1) |
limit | integer | Results per page (default: 25, max: 100) |
search | string | Full-text search across log fields |
userId or actorId | string | Filter by actor (user/agent/admin) |
targetId | string | Filter by target resource |
resourceType or targetType | string | Filter by target resource type |
action | string | Filter by action (e.g. login) |
severity | string | Filter by severity (info, warning, error) |
eventType | string | Filter by event type (e.g. user.login) |
startDate or from | ISO 8601 | Start of date range |
endDate or to | ISO 8601 | End of date range |
ipAddress | string | Filter by source IP address |
{
"data": [
{
"id": 4244,
"createdAt": "2026-03-01T08:00:00Z",
"eventType": "user.login",
"action": "login",
"severity": "info",
"actorId": "019d6190-9bba-7b5f-a0a5-07a6d10184d2",
"actorName": "Alice",
"actorType": "user",
"targetType": "user",
"targetId": "019d6190-9bba-7b5f-a0a5-07a6d10184d2",
"ipAddress": "203.0.113.1",
"status": "success"
}
],
"meta": {
"total": 2847,
"page": 1,
"limit": 50,
"totalPages": 57,
"hasNextPage": true,
"hasPreviousPage": false
},
"resourceType": "audit_logs"
}
Get Single Log Entry
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/01JF3KAUDIT..." \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/01JF3KAUDIT...", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/01JF3KAUDIT...",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/01JF3KAUDIT...", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Audit Log Statistics
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/stats" \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/stats", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/stats",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/stats", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Query Parameters for Stats
| Parameter | Description |
|---|---|
from | Start of time window (ISO 8601) |
to | End of time window (ISO 8601) |
The response groups counts by event type, action and severity.
List Available Actions
Returns a distinct list of all action types present in the audit log for the organization.
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/actions" \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/actions", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/actions",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/actions", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
{
"data": ["authenticate", "create", "delete", "login", "revoke", "update"]
}
Export Audit Logs
Export is synchronous: the endpoint streams the file back directly with Content-Disposition: attachment (HTTP 200). Use the -o/--output flag (or your client's stream/download API) to save it — the body is the CSV/JSON file itself, not a JSON envelope.
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/export?format=csv&startDate=2026-01-01T00:00:00Z&endDate=2026-03-31T23:59:59Z" \
-H "Authorization: ApiKey lmk_abc123" \
-o audit-logs-q1-2026.csv
import { writeFile } from "node:fs/promises";
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/export?format=csv&startDate=2026-01-01T00:00:00Z&endDate=2026-03-31T23:59:59Z", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH }
});
// Response body is the file itself, not JSON.
await writeFile("audit-logs-q1-2026.csv", Buffer.from(await resp.arrayBuffer()));
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/export?format=csv&startDate=2026-01-01T00:00:00Z&endDate=2026-03-31T23:59:59Z",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
# Response body is the file itself, not JSON.
with open("audit-logs-q1-2026.csv", "wb") as f:
f.write(resp.content)
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/export?format=csv&startDate=2026-01-01T00:00:00Z&endDate=2026-03-31T23:59:59Z", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/export?format=json&startDate=2026-01-01T00:00:00Z&endDate=2026-03-31T23:59:59Z" \
-H "Authorization: ApiKey lmk_abc123" \
-o audit-logs-q1-2026.json
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/export?format=json&startDate=2026-01-01T00:00:00Z&endDate=2026-03-31T23:59:59Z", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH }
});
// `format=json` returns a JSON file as the body; parse it directly or save it.
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/export?format=json&startDate=2026-01-01T00:00:00Z&endDate=2026-03-31T23:59:59Z",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
# `format=json` returns a JSON file as the body; parse it directly or save it.
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/export?format=json&startDate=2026-01-01T00:00:00Z&endDate=2026-03-31T23:59:59Z", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Export Parameters
| Parameter | Values | Default |
|---|---|---|
format | csv, json | csv |
startDate / from | ISO 8601 | — |
endDate / to | ISO 8601 | — |
Exports are limited to 10,000 records per request.
Retention Settings
Get Retention Settings
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/retention" \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/retention", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/retention",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/retention", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
{
"data": {
"retentionDays": 90,
"autoDelete": true
}
}
Update Retention Settings
- curl
- Node
- Python
- Go
curl -X PUT https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/retention \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{
"retentionDays": 365,
"autoDelete": true
}'
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/retention", {
method: "PUT",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" },
body: JSON.stringify({
"retentionDays": 365,
"autoDelete": true
})
});
console.log(await resp.json());
import os, requests
resp = requests.put(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/retention",
headers={"Authorization": os.environ["LUMO_AUTH"], "Content-Type": "application/json"},
json={
"retentionDays": 365,
"autoDelete": True
}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
body := strings.NewReader(`{
"retentionDays": 365,
"autoDelete": true
}`)
req, _ := http.NewRequest("PUT", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/audit-logs/retention", body)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
autoDelete: false keeps audit rows forever. With autoDelete: true, rows older than
retentionDays (never less than 90) are pruned by a daily platform job, but
only when the organization has data retention enabled in its GDPR settings. See
Log retention.
Common Event Types
| Event | Description |
|---|---|
user.login | Successful user sign-in (browser session created) |
login.failed | Failed password, passkey, magic-link or push sign-in |
session.revoked | A session ended: sign-out, or revoked by the user or an administrator |
user.created | New user provisioned |
user.updated | User profile updated |
user.deleted | User deleted (a full GDPR erasure; gdpr.erasure.completed is written too) |
user.blocked | User account blocked |
user.password_changed | Password changed (metadata.source says how) |
mfa.enabled | A user enrolled their first MFA factor |
mfa.challenge.succeeded / mfa.challenge.failed | MFA challenge passed / failed |
mfa.push.reported_fraud | A user reported a push request as "This wasn't me" |
mfa.tap.issued | An admin issued a temporary access code |
mfa.policy.changed | The organization MFA policy changed |
oauth.access_token_issued | OAuth tokens issued (authorization code, CIBA or device code grant) |
token.revoked | OAuth tokens revoked by an administrator |
client.created | OAuth client registered |
admin.tenant.*, admin.user.* | Action taken on this organization or its users by LumoAuth operators |
role.assigned | Role granted to a user or group |
oauth.authorization_issued | A user completed authorization and the application received its response |
Every event type LumoAuth writes is listed, by category, in
Audit logs → Event categories.
The full list of mfa.* events is in Audit logs → MFA events.
Privilege changes, signing keys, agents, webhooks, revocations, exports and failed
authentication are listed under
Security-relevant events.
password_reset_requested was renamed to password_reset.requested on 2026-09-25; update
saved eventType filters.
Actor fields
actorType is user, api_key, agent, scim or system, and authMethod is
session, api_key, bearer, scim, agent or system. For API-key and agent actors,
metadata.actor_owner_id holds the user who created the key or owns the agent, and
metadata.api_key_id / metadata.oauth_client_id identify the credential. See
Who did it.
On single entries, oldValue / newValue now carry the recorded before/after values
(they were always null before 2026-09-25), and changes returns the raw change set.
Changes LumoAuth operators make to an organization or its users are written as admin.tenant.*, admin.user.* and billing.admin_* events in that organization's log, and admin.tenant.* / admin.user.* always raise a high-risk alert.