Verify the audit chain
Prove the audit trail has not been edited or truncated.
Prove the audit trail has not been edited or truncated.
Each row's hash covers its own contents AND its position in the chain, so three different tamperings produce three different findings:
- editing a row ->
hash_mismatchon that row; - deleting a row from the middle ->
broken_linkon its successor ANDmissing_recordfor the gap, because re-linking would require rewriting every later hash and each rewrite is itself a mismatch; - deleting the newest rows ->
missing_recordagainst the chain head.
Legitimate retention pruning is NOT a discrepancy: the sweep records how far it pruned and the surviving chain links to that record, so a trail that starts mid-sequence still verifies clean.
BOUNDED AND RESUMABLE. At most limit rows per call; when next_seq is
non-null there is more to check, so pass it back as start_seq. A year of
rows is verified in pages rather than in one query that times out.
Declared before GET /v2/audit-logs/{event_id}: registered the other way
round, this path would arrive there as event_id="verify".
Errors: 401, 403 (missing audit:read), 429, 503.
Rate limit: 1000 requests per minute per organization (bucket default). Exceeding it returns 429 with Retry-After; the X-RateLimit-* response headers report your remaining allowance on every call.
v2/audit-logs/verifyAuthorization
AuthorizationRequiredBearer <token>
Your Graine API key. Create one in the dashboard under Developers, or via POST /v2/api-keys. Send it as Authorization: Bearer <key>.
In: header
Query Parameters
start_seqStart Seq
Resume from this sequence number. Use the next_seq from the previous call.
1limitLimit
Rows to walk in this call.
500Minimum: 1Maximum: 2000organization_idOrganization Id
Must match the organisation the API key belongs to.
Response Body
200
Every discrepancy found in the window that was walked.
organization_idRequiredOrganization Id
The organization whose chain was walked.
validRequiredValid
True when this window contained no discrepancies at all.
checkedRequiredChecked
Rows examined in this call.
first_seqFirst Seq | null
First sequence number examined in this call.
last_seqLast Seq | null
Last sequence number examined in this call.
head_seqHead Seq | null
The newest sequence number the chain head knows about.
pruned_through_seqPruned Through Seq | null
Rows at or below this sequence were removed by the retention sweep, not by tampering. A chain that starts just above it and links to the recorded pruned hash verifies clean.
problemsRequiredProblems
Empty when 'valid' is true.
next_seqNext Seq | null
Pass as start_seq to continue. Null when the whole chain has been walked.
verified_atVerified At | null
ISO-8601 UTC.
401
1100 — missing, unknown or inactive API key. 1101 — a browser session token was presented.
errorRequiredError
Stable integer code from the /v2 error table. Branch on this.
messageRequiredMessage
One human-readable sentence. Wording may change; the code will not.
403
1102 — the key is valid but lacks the audit:read scope, or names another organisation.
errorRequiredError
Stable integer code from the /v2 error table. Branch on this.
messageRequiredMessage
One human-readable sentence. Wording may change; the code will not.
422
1001 — a query parameter failed validation; the message names the field.
errorRequiredinteger
Stable integer code from the error table. Branch on this, not on the message.
messageRequiredstring
One human-readable sentence. Wording may change; the code will not.
429
1300 — per-organisation rate limit exceeded. Carries Retry-After.
errorRequiredError
Stable integer code from the /v2 error table. Branch on this.
messageRequiredMessage
One human-readable sentence. Wording may change; the code will not.
503
1501 — the audit log could not be read. Deliberately NOT an empty page.
errorRequiredError
Stable integer code from the /v2 error table. Branch on this.
messageRequiredMessage
One human-readable sentence. Wording may change; the code will not.
Every discrepancy found in the window that was walked.

