Surface 05 · CanaryAudit
Verify an audit record
Every logged action is sealed with a SHA-256 content hash at write time. Verification recomputes the hash from the stored payload and compares. There are two ways to run that check and they are not interchangeable. POST /canaryaudit/verify/{log_id} is the owner path: it requires authentication and ownership of the record, and anybody else gets a 401 or a 403 and no verification data. The public path is GET /api/verify/{content_hash} — no auth, 30 requests per minute per IP, and it takes the content hash, not the record id.
So the thing you hand a customer or an auditor is the 64-character content_hash returned by POST /canaryaudit/log, never the record id. The same check has a web page at https://www.canaryvaults.com/verify, which accepts a 64-character hex hash and nothing else — paste a UUID record id and it answers "unrecognized format" without ever reaching the backend.
A record edited after sealing returns "tampered": true with both hashes so you can see the mismatch. The hash covers user_id|agent_id|action_type|input_trigger|action_detail|outcome|logged_at — metadata is deliberately outside it, so editing metadata does not break verification. The extra HMAC seal is opt-in on a server-side key, which is why an untampered record can legitimately report "sealed": false. Exports are JSON on every plan and legal PDF on Pro — tamper-evident, not court-admissible.