Audit & integrity

Every consequential action on an AgenticBoxes account writes an audit event, and every audit event is hash-chained to the one before it. A reconciliation job re-walks each account's chain on a schedule, confirms nothing was edited, deleted, or reordered, and writes the result below. The score is published as raw, machine-readable JSON, written by the reconciliation job itself — so the number here is the job's own output on a schedule, not a figure we hand-edit. (What it does and doesn't prove is spelled out below.)

Loading the live score…

The last runs

Each row is one reconciliation pass. Newest first.

When (UTC)CheckChains intactBrokenEvents
Loading run history…

What the chain proves — and what it doesn't

Each audit event carries a _chain object: its sequence number, the hash of the event before it, and its own hash — sha256 over the event's canonical contents plus that previous hash. Change any historical event's content, drop one, or swap two, and every hash downstream stops matching. The reconciliation job re-walks the whole chain from a known anchor every run; a single altered byte turns Chains intact red and Tampering detected non-zero. The job can't quietly skip itself either — missed runs show up as gaps.

Be precise about the guarantee: the chain makes any change to a recorded event detectable after the fact. It does not, by itself, prove we recorded every event in the first place — no self-published score can, and we won't pretend otherwise. For independent proof of a specific message, that's what the evidence envelope (below) is for: a signed per-message record you can corroborate against the recipient's own copy via its Message-ID, without trusting us. The score is the platform's tamper-evident self-check; the envelope is your independent receipt.

The number isn't a figure we hand-edit — it's emitted by the reconciliation job as the JSON this page reads, every run. Read it yourself:

curl https://docs.agenticboxes.email/audit.json

Request a signed audit bundle for one of your messages

The score above is the platform grading itself. You can also request a cryptographically-signed audit of your account's traffic — the evidence trail for a specific message. We're running this as an open beta: during the beta, message audits are free, and we currently honor requests for any message sent or received in the last 7 days (a 7-day-in-arrears window while we tune retention).

Call the endpoint with your account's API key (admin scope). It returns the signed bundle as JSON:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://api.agenticboxes.email/api/v1/account/audit/message/<message_id>

The bundle includes message (your message's identity + the SES message id), evidence (storage timestamp + SES acceptance), signing (the key, algorithm, canonicalization), and a base64 signature.

Verifying a bundle

Verification is two steps: get the public key, then verify the signature over a deterministic canonical form of the bundle. There are two ways to get the key, and they're not equivalent on trust:

  1. Canonical — ask AWS directly (any AWS account works). The bundle carries the signing key's full ARN at signing.key_arn; the key's resource policy grants kms:GetPublicKey to all principals, so any verifier with AWS credentials can fetch the PEM from KMS:
    aws kms get-public-key --key-id <signing.key_arn>
    This roots the trust in AWS — not in a key file we host on our own domain. We can't substitute a different key without losing access to our own, and any key state change is recorded in CloudTrail.
  2. Convenience — ask our API (you, the bundle owner). Same PEM, fetched via your API key. Useful when forwarding a bundle to a counterparty who doesn't have AWS credentials — you hand them the bundle plus the PEM:
    curl -H "Authorization: Bearer YOUR_API_KEY" \
      https://api.agenticboxes.email/api/v1/account/audit/public-key
    This endpoint dogfoods kms:GetPublicKey on every call, so the PEM is always in sync with what KMS would return for the ARN — but the trust root is us, not AWS. For high-stakes verification, the AWS path is the one to use.

Once you have the PEM, strip the signature field from the bundle, recompute the canonical JSON over what's left (recursive sort-keys, compact JSON.stringify, UTF-8), then verify the base64-decoded signature against those bytes with ECDSA_SHA_256 + the public key. Canonicalization is deterministic, so any verifier reproduces the exact bytes we signed.

Verifying the DKIM evidence (received-direction bundles)

For direction=received the bundle additionally carries DKIM evidence under message.dkim. That gives you an independent, key-independent cryptographic chain — the sender's MTA signed the email at send time with a key in DNS we don't control once published, so verifying DKIM proves the headers (and body) really came from the sender, without trusting our attestation at all. There are two halves to verify, header signature and body hash, and they're independent:

  1. Header signature (RFC 6376 §5). Each entry in message.dkim.signatures[] pairs the raw DKIM-Signature header value with the published public key as we resolved it at bundle-issue time (public_key_txt) — so verification doesn't depend on the sender still publishing that selector when you verify. Per the signature's h= tag, canonicalize the named headers from signed_headers using the c='s header canonicalization (relaxed or simple), then RSA-verify the signature's b= bytes against that hash with the public key in public_key_txt.
  2. Body hash (bh=). The bundle does NOT carry the message body — we keep it out so the bundle stays forwardable without privacy loss. As the bundle owner, fetch the original RFC 5322 bytes:
    curl -H "Authorization: Bearer YOUR_API_KEY" \
      https://api.agenticboxes.email/api/v1/messages/<message_id>/raw
    Strip everything up to (and including) the first blank line — that's the body. Apply the body canonicalization in the signature's c= tag (relaxed or simple), SHA-256 the result, base64-encode. Compare to the signature's bh= value. Match means the body in our store is byte-equivalent to what the sender signed — i.e. we haven't tampered with it post-receipt.

Both halves verifying without our public key in the chain is the "independent witness" upgrade — your confidence in the message no longer rests on our attestation. For direction=sent, message.dkim is currently {not_applicable:true}; SES adds its DKIM after our send call returns and we don't retain those bytes today. (Sent-side passthrough is on the roadmap.)

What the bundle proves — and what it doesn't

We try to keep the framing honest. A signed bundle is real evidence, but it is not the same thing as independent third-party proof. Read the bundle for what it is:

Short version: credible vendor-attested receipt, tamper-evident, useful internally and for cooperative counterparties. For court-grade evidence today, pair it with the recipient's copy of the email. The deeper-audit tier (AWS-signed CloudTrail bundle + DKIM passthrough) closes most of this gap; it's on the roadmap.

Prefer email? You can also reach support@agenticboxes.email and we'll reply with the bundle — same beta, same free, same 7-day window. The endpoint is the canonical path; email is the human fallback.

Embed the badge

Add this one line wherever you want the badge to appear — it renders a live integrity pill (reading the same audit.json) right there:

<script src="https://docs.agenticboxes.email/badge.js" async></script>

That script is all you need. To place the badge somewhere other than where the script sits — a global header or footer, say — keep the script and also drop this empty span at the spot you want; the badge fills it in:

<span id="agenticboxes-audit-badge"></span>

Live render:

See the receipts → Raw JSON