Verify the audit log's hash chains
The audit_log.verify operation: GET /api/v1/audit_log/verify in the REST API and the audit_log_verify tool in the MCP server.
/api/v1/audit_log/verifyChecks the organization's audit log hash chains now and returns their status. The log is append-only and
hash-chained: each entry's hash covers the previous entry's hash and the entry itself, in 16 chains per
organization (an entry's chain is chosen by its target). A changed, deleted, reordered or forged entry breaks its
chain.
Each chain is checked over its newest 100 entries (links, hashes, the digests of the
personal data kept outside the hash, and the chain's head); a shorter chain is checked from its first entry, and
then complete is true. chains says, per chain, how many entries it holds and which positions verified
(verifiedFrom to verifiedTo), or where it broke; brokenAt names the first break, in chain order (or
entryId: null when the chain's head is the problem).
Every entry of every chain, including those older than your plan's visibility window, is also verified each night
by Ownrecord, whose staff are alerted to a break (AUDIT_CHAIN_BROKEN). nightly is the latest of those
verifications: when it ran, how many entries verified, and where it found a break (null before the first one).
status is broken when the check now or the latest nightly verification found a break.
At a glance
- REST:
GET /api/v1/audit_log/verify - MCP tool:
audit_log_verify - Operation ID:
audit_log.verify - Scope: Organization
- Permission:
audit:read - Risk tier:
read - Dry run: Not supported
- Idempotency: Not needed (read only)
- Modes: Test and live
- Authentication: API key with the
audit:readscope - Since: M10
Request
Query parameters
This operation takes no query parameters.
Response
200 OK with a JSON object:
| Field | Type | Description |
|---|---|---|
object | "audit_log_verification" | |
status | one of intact, broken | |
complete | boolean | |
window | integer (at least 1) | |
entries | integer (at least 0) | |
checkedEntries | integer (at least 0) | |
checkedAt | string | |
brokenAt | object or null | |
chains | array of object | |
chains[].shard | integer (at least 0) | |
chains[].chain | string | |
chains[].entries | integer (at least 0) | |
chains[].verifiedFrom | integer (at least 0) or null | |
chains[].verifiedTo | integer (at least 0) or null | |
chains[].status | one of intact, broken, empty | |
chains[].brokenAt | string or null | |
chains[].reason | string or null | |
nightly | object or null |
Examples
An intact audit log
Every chain is longer than the window, so the newest 100 entries of each were checked.
GET /api/v1/audit_log/verify
Authorization: Bearer or_test_…
Response (200):
{
"object": "audit_log_verification",
"status": "intact",
"complete": false,
"window": 100,
"entries": 3720,
"checkedEntries": 1600,
"checkedAt": "2026-11-02T09:20:00.000Z",
"brokenAt": null,
"chains": [
{
"shard": 0,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3",
"entries": 180,
"verifiedFrom": 81,
"verifiedTo": 180,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 1,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#1",
"entries": 187,
"verifiedFrom": 88,
"verifiedTo": 187,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 2,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#2",
"entries": 194,
"verifiedFrom": 95,
"verifiedTo": 194,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 3,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#3",
"entries": 201,
"verifiedFrom": 102,
"verifiedTo": 201,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 4,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#4",
"entries": 208,
"verifiedFrom": 109,
"verifiedTo": 208,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 5,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#5",
"entries": 215,
"verifiedFrom": 116,
"verifiedTo": 215,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 6,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#6",
"entries": 222,
"verifiedFrom": 123,
"verifiedTo": 222,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 7,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#7",
"entries": 229,
"verifiedFrom": 130,
"verifiedTo": 229,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 8,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#8",
"entries": 236,
"verifiedFrom": 137,
"verifiedTo": 236,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 9,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#9",
"entries": 243,
"verifiedFrom": 144,
"verifiedTo": 243,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 10,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#10",
"entries": 250,
"verifiedFrom": 151,
"verifiedTo": 250,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 11,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#11",
"entries": 257,
"verifiedFrom": 158,
"verifiedTo": 257,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 12,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#12",
"entries": 264,
"verifiedFrom": 165,
"verifiedTo": 264,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 13,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#13",
"entries": 271,
"verifiedFrom": 172,
"verifiedTo": 271,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 14,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#14",
"entries": 278,
"verifiedFrom": 179,
"verifiedTo": 278,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 15,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#15",
"entries": 285,
"verifiedFrom": 186,
"verifiedTo": 285,
"status": "intact",
"brokenAt": null,
"reason": null
}
],
"nightly": {
"status": "intact",
"checkedAt": "2026-11-02T03:00:04.512Z",
"entries": 3708,
"brokenAt": null
}
}
A break in older entries, found at night
The newest entries verify now, but last night's verification of every entry found a changed older entry.
GET /api/v1/audit_log/verify
Authorization: Bearer or_test_…
Response (200):
{
"object": "audit_log_verification",
"status": "broken",
"complete": false,
"window": 100,
"entries": 3720,
"checkedEntries": 1600,
"checkedAt": "2026-11-02T09:20:00.000Z",
"brokenAt": null,
"chains": [
{
"shard": 0,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3",
"entries": 180,
"verifiedFrom": 81,
"verifiedTo": 180,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 1,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#1",
"entries": 187,
"verifiedFrom": 88,
"verifiedTo": 187,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 2,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#2",
"entries": 194,
"verifiedFrom": 95,
"verifiedTo": 194,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 3,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#3",
"entries": 201,
"verifiedFrom": 102,
"verifiedTo": 201,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 4,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#4",
"entries": 208,
"verifiedFrom": 109,
"verifiedTo": 208,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 5,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#5",
"entries": 215,
"verifiedFrom": 116,
"verifiedTo": 215,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 6,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#6",
"entries": 222,
"verifiedFrom": 123,
"verifiedTo": 222,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 7,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#7",
"entries": 229,
"verifiedFrom": 130,
"verifiedTo": 229,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 8,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#8",
"entries": 236,
"verifiedFrom": 137,
"verifiedTo": 236,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 9,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#9",
"entries": 243,
"verifiedFrom": 144,
"verifiedTo": 243,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 10,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#10",
"entries": 250,
"verifiedFrom": 151,
"verifiedTo": 250,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 11,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#11",
"entries": 257,
"verifiedFrom": 158,
"verifiedTo": 257,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 12,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#12",
"entries": 264,
"verifiedFrom": 165,
"verifiedTo": 264,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 13,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#13",
"entries": 271,
"verifiedFrom": 172,
"verifiedTo": 271,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 14,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#14",
"entries": 278,
"verifiedFrom": 179,
"verifiedTo": 278,
"status": "intact",
"brokenAt": null,
"reason": null
},
{
"shard": 15,
"chain": "org_01m4ttwd3veh9apbewxheq5xh3#15",
"entries": 285,
"verifiedFrom": 186,
"verifiedTo": 285,
"status": "intact",
"brokenAt": null,
"reason": null
}
],
"nightly": {
"status": "broken",
"checkedAt": "2026-11-02T03:00:04.512Z",
"entries": 0,
"brokenAt": {
"entryId": "aud_01m2q8x7c1e9vbs3k0a5f7h2jd",
"reason": "hash mismatch: the entry was changed after it was written"
}
}
}
Errors
| Code | HTTP status | When | What to do |
|---|---|---|---|
permission_denied | 403 | You can't read the organization's audit log, or you're limited to some of its entities or brands (the chains are the whole organization's). | Ask an organization owner or admin for a role or scope that allows this action. |
It can also return these common errors: validation_failed, api_version_unsupported, authentication_required, api_key_invalid, resource_not_found, dry_run_not_supported, rate_limited, internal_error.