Audit log
The audit log records who changed what. These endpoints read it, check that nobody has changed it, and export it for an auditor. Nothing here writes to the log except the export, which is itself recorded.
Admins and reviewers can use them, signed in as themselves. Analysts, engineers and API keys get 403. The examples put a session token in $SENTINEL_SESSION.
List entries
GET /v1/auditEntries newest first, a page at a time. Filters combine.
sentinel, setup, bootstrap and sentinel-admin for what Sentinel did itself.case. or alert.closure.alert, api_key, audit_log, case, entity, organisation, report, rule, screening, user, webhook or webhook_delivery.next_cursor to send back as cursor.curl "$SENTINEL_URL/v1/audit?action_prefix=alert.&limit=1" \
-H "Authorization: Bearer $SENTINEL_SESSION"
{
"entries": [
{
"seq": 11,
"at": "2026-10-18T09:06:38.327186Z",
"actor": {
"id": "1ed6f977-132d-4083-b2be-755c9950c66c",
"name": "Kofi Mensah",
"role": "analyst"
},
"action": "alert.updated",
"object": {
"kind": "alert",
"id": "b5d48a35-27a2-4a2c-b540-79604b05d24b"
},
"data": {
"status": "in_review"
},
"hash": "09dcfb9d1cef52ba24cf2f7191616c34d830c0636fef7bb35ed24c37819e206a",
"prev_hash": "5a0c7655522d2357fcd97b561ea5f261615c2ddf027ca92def64884643f65742"
}
],
"next_cursor": "MTc5MjMxNDM5ODMyNzE4NjAwMHwxMQ",
"total": 3
}
| Field | Meaning |
|---|---|
total | How many entries match the filters, across every page |
actor.name | The person's or key's name. Missing when Sentinel did it. |
actor.role | The person's role today, not when they acted. key for an API key, system for Sentinel. |
data | What changed, as recorded at the time. Empty when nothing more was recorded. |
prev_hash | The previous entry's hash. Missing on the first entry. |
Check the chain
GET /v1/audit/chainWalks the organisation's whole chain now, like sentinel-admin audit verify, and says whether every entry still matches its hash and the one before it.
{
"verified": true,
"entries": 11,
"head": "09dcfb9d1cef52ba24cf2f7191616c34d830c0636fef7bb35ed24c37819e206a",
"checked_at": "2026-10-18T09:06:38.549815Z"
}
When an entry doesn't match, verified is false, first_bad_seq is that entry, entries counts the entries before it, and head is the last entry that matched:
{
"verified": false,
"entries": 10,
"first_bad_seq": 11,
"head": "5a0c7655522d2357fcd97b561ea5f261615c2ddf027ca92def64884643f65742",
"checked_at": "2026-10-18T09:06:38.679228Z"
}
head is missing while the log is empty.
Export as CSV
GET /v1/audit/export.csvThe entries that match the same filters as the list, oldest first, as a CSV download. One export holds at most 100,000 entries. When more match, it returns 400 and you narrow the dates or filters.
curl -OJ "$SENTINEL_URL/v1/audit/export.csv?since=2026-07-01T00:00:00Z&until=2026-10-01T00:00:00Z" \
-H "Authorization: Bearer $SENTINEL_SESSION"
seq,time,actor,actor_name,role,action,object_kind,object_id,data,hash,prev_hash
5,2026-10-18T09:06:36.523326Z,d77db631-c9d8-41bd-933a-d7deaae4b782,Admin,admin,user.created,user,ecf0ce94-4c0b-4b2e-9a80-ee79370cfa3b,"{""email"":""akosua@example.com"",""role"":""reviewer""}",84166182…,7694f1bc…
data is the JSON as it was recorded. A name, ID or data value that starts with =, +, - or @ gets a leading ', so a spreadsheet shows it as text instead of running it.
Every export adds an audit.exported entry with the filters and the number of rows.
Errors
| Code | error |
|---|---|
400 | since must be before until |
400 | limit must be between 1 and 200 |
400 | cursor isn't one this API gave out |
400 | more than 100000 entries match; narrow the dates or filters |
403 | your role doesn't allow this |
403 | API keys can't do this; sign in to the dashboard as a person |