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
/audit/status
{"data": {"enabled": true, "available": true, "coverage": "standard", "retention": {"days": 90, "max_rows": 100000}, "total": 1284}}
Response Codes
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
/audit/events
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. |
{"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
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
/audit/facets
{"data": {"events": ["config.update", "page.update", "user.login", "user.login.failed"], "actors": [{"id": "admin", "name": "Admin User"}]}}
Response Codes
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
/audit/export
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. |
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
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.