Skip to content

Powered by Grav + Helios

Audit Trail

Audit Trail

Read the admin audit trail: a log of the actions taken through the API, with who did them, when, from which IP and against what. Because Admin Next does everything through the API, the log covers the whole of Admin Next plus any API-key clients. Actions taken in the classic admin that bypass the API...

Read the admin audit trail: a log of the actions taken through the API, with who did them, when, from which IP and against what. Because Admin Next does everything through the API, the log covers the whole of Admin Next plus any API-key clients. Actions taken in the classic admin that bypass the API are not recorded.

The trail is off by default. Turn it on with plugins.api.audit.enabled; it needs the pdo_sqlite PHP extension. plugins.api.audit.coverage sets the detail level (standard, or detailed to also keep field-level before/after values for page edits), retention_days and retention_max_rows bound how much is kept, and anonymize_ip masks the last part of stored IPs.

Permissions: every route needs a super admin with api.super. An account that only holds admin.super is refused, and a scoped API key needs the admin.super scope, the same as every other super-only endpoint. Demo accounts get 403 with "The audit trail is hidden in demo mode.", since on a public demo the log holds other visitors' IPs and user agents.

Except for Audit Status, the routes return 404 while the trail is disabled and 503 when SQLite is missing.

Filters

List Audit Events and Export Audit Log accept the same filters, all optional and combined with AND:

Parameter Matches
event Exact event code, e.g. page.update
actor Exact actor id or actor name
target_type Exact target type, e.g. page, media, user, group, config, package
severity Exact severity: recorded events use info, notice or warning
from Earliest timestamp, inclusive, in Unix epoch milliseconds
to Latest timestamp, inclusive, in Unix epoch milliseconds
q Case-insensitive substring of the actor name, target id, event code or IP

Event fields

Each event has an id, a ts in Unix epoch milliseconds, the event code (namespace.action), a severity, the actor (actor_id, actor_name, actor_roles), the auth_method (apikey, jwt or session), the client ip and user_agent, the target_type and target_id, a status that is reserved and currently always null, and an event-specific context object, or null when the event has no extra detail.

Audit Status

GET /audit/status
Whether the audit trail is available and enabled, with its detail level and retention settings. Admin Next uses it to decide whether to show the Audit tab. Unlike the other audit routes it works while the trail is disabled. Requires a super admin with `api.super` (a scoped API key needs the `admin.super` scope); demo accounts get 403.
JSON
{"data": {"enabled": true, "available": true, "coverage": "standard", "retention": {"days": 90, "max_rows": 100000}, "total": 1284}}

Response Codes

200 Status returned.
401 Unauthorized.
403 Not a super admin with `api.super`, a scoped key without `admin.super`, or a demo account.

available says whether the pdo_sqlite extension is loaded, and enabled is true only when plugins.api.audit.enabled is on and SQLite is available. coverage is standard or detailed. In retention, days prunes older entries and max_rows caps the number stored; 0 turns either bound off. total is the number of stored events, or null when the trail is disabled or the count failed.

List Audit Events

GET /audit/events
Paginated audit events, newest first, narrowed by the optional filters. The pagination `links` keep the active filters, so following `next` on a filtered view stays filtered. Requires a super admin with `api.super` (a scoped API key needs the `admin.super` scope); demo accounts get 403.

Parameters

Name Type Description
page optional integer Page number (default 1).
per_page optional integer Events per page. Defaults to 50, capped at `plugins.api.pagination.max_per_page` (default 1000).
event optional string Exact event code, e.g. `page.update`.
actor optional string Exact actor id or actor name.
target_type optional string Exact target type, e.g. `page`, `user` or `config`.
severity optional string Exact severity (`info`, `notice` or `warning`).
from optional integer Earliest timestamp, inclusive, in Unix epoch milliseconds.
to optional integer Latest timestamp, inclusive, in Unix epoch milliseconds.
q optional string Case-insensitive substring of the actor name, target id, event code or IP.
JSON
{"data": [{"id": 42, "ts": 1758441600000, "event": "page.update", "severity": "info", "actor_id": "admin", "actor_name": "Admin User", "actor_roles": ["admin"], "auth_method": "jwt", "ip": "127.0.0.1", "user_agent": "Mozilla/5.0", "target_type": "page", "target_id": "/blog/my-first-post", "status": null, "context": {"changes": {"title": {"old": "Hello", "new": "My First Post"}}}}], "meta": {"pagination": {"page": 1, "per_page": 50, "total": 1, "total_pages": 1}}, "links": {"self": "/api/v1/audit/events?page=1&per_page=50&event=page.update"}}

Response Codes

200 Events returned.
401 Unauthorized.
403 Not a super admin with `api.super`, a scoped key without `admin.super`, or a demo account.
404 The audit trail is not enabled.
503 The SQLite PHP extension is not installed.

See the Audit Trail introduction for the event fields. context carries a few scalar hints from the event when present (method, reason, old_route, new_route, lang, version, bytes), and with coverage: detailed a page update also carries changes, the before and after value of each changed field, as in the example.

Audit Facets

GET /audit/facets
The distinct event codes and actors found in the log, sorted, for the filter drop-downs. Requires a super admin with `api.super` (a scoped API key needs the `admin.super` scope); demo accounts get 403.
JSON
{"data": {"events": ["config.update", "page.update", "user.login", "user.login.failed"], "actors": [{"id": "admin", "name": "Admin User"}]}}

Response Codes

200 Facets returned.
401 Unauthorized.
403 Not a super admin with `api.super`, a scoped key without `admin.super`, or a demo account.
404 The audit trail is not enabled.
503 The SQLite PHP extension is not installed.

An actor's id and name are null for events recorded without a known account. On a failed login both hold the username that was tried.

Export Audit Log

GET /audit/export
Download every event matching the filters, newest first and capped at 100,000 rows, as an attachment (`audit-log.csv` or `audit-log.json`) sent with `Cache-Control: no-store`. The file is not wrapped in the JSON envelope. Requires a super admin with `api.super` (a scoped API key needs the `admin.super` scope); demo accounts get 403.

Parameters

Name Type Description
format optional string `csv` (default) or `json`. Any other value falls back to CSV.
event optional string Exact event code, e.g. `page.update`.
actor optional string Exact actor id or actor name.
target_type optional string Exact target type, e.g. `page`, `user` or `config`.
severity optional string Exact severity (`info`, `notice` or `warning`).
from optional integer Earliest timestamp, inclusive, in Unix epoch milliseconds.
to optional integer Latest timestamp, inclusive, in Unix epoch milliseconds.
q optional string Case-insensitive substring of the actor name, target id, event code or IP.
JSON
id,ts,event,severity,actor_id,actor_name,actor_roles,auth_method,ip,user_agent,target_type,target_id,status,context
42,1758441600000,page.update,info,admin,"Admin User","[""admin""]",jwt,127.0.0.1,Mozilla/5.0,page,/blog/my-first-post,,

Response Codes

200 The log file, as `text/csv` or `application/json`.
401 Unauthorized.
403 Not a super admin with `api.super`, a scoped key without `admin.super`, or a demo account.
404 The audit trail is not enabled.
503 The SQLite PHP extension is not installed.

The CSV columns are id, ts, event, severity, actor_id, actor_name, actor_roles, auth_method, ip, user_agent, target_type, target_id, status, context. Array values such as actor_roles and context are JSON-encoded, and a cell starting with =, +, -, @, a tab or a carriage return is prefixed with ' so spreadsheet apps don't run it as a formula.

With format=json the file is a bare JSON array of events with the same fields as List Audit Events.