Running Keel Verify
This walkthrough shows how a signed audit export is independently verifiable against exported signing material, without relying on a Keel dashboard, support response, or private Keel system.
Use a clean terminal on a machine where you can preserve the original downloaded files. Do not edit or reformat the signed payload. Extracting the downloaded proof-pack ZIP copies its entries without changing their bytes; preserve the original ZIP too. Do not recompress the signed export payload.
1. Install keel-verify
Use the current released verifier, pinned exactly.
python -m pip install keel-verifier==3.28.0Confirm that the CLI is available.
keel-verify --helpkeel-verify export renders the human-readable AI Permit report by default in
version 3.4.3 and later. Use --raw only when an existing automation needs the
legacy technical output; use --json for machine-readable results.
Version 3.6.0 and later also verifies self-contained AI Permit-to-Work evidence packs:
keel-verify work-chain work-evidence.jsonThe bare path keel-verify work-evidence.json auto-detects the same profile.
The Work report adjudicates authority-manifest integrity, exact child
containment, dispatch-boundary liveness, and payment-value conservation. It
also separates authorization, dispatch, external outcome, and settlement.
work-chain.v1 is scope-faithful through its declared cutoff and checkpoint.
Successful verification does not claim comprehensive real-world activity,
Policy correctness, business-job completion, provider success, or settlement
without the required supporting evidence.
2. Request a signed export from your Keel admin
Signed audit export access requires Production or Enterprise. Ask your Keel admin to create an audit export with chain entries included, using a project API key authorized for this workflow.
POST /v1/compliance/exports?include_chain_entries=trueFor a scoped review, specify the relevant time window and an audit-grade export type such as full_audit, hipaa_audit, soc2_evidence, or incident_evidence.
Set FROM and TO to the UTC timestamps bounding the customer journey you are reviewing (for example, from its first request through its final closure). With curl and jq installed:
jq -n --arg from "$FROM" --arg to "$TO" '{
export_type: "full_audit", format: "json",
filters: {occurred_at_from: $from, occurred_at_to: $to}
}' > export-request.json
curl --fail-with-body -sS -X POST "https://api.keelapi.com/v1/compliance/exports?include_chain_entries=true" \
-H "Authorization: Bearer $KEEL_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @export-request.json > export-created.json
EXPORT_ID=$(jq -er '.export_id' export-created.json)The create response is 202 and queues the export. Read its status at GET /v1/compliance/exports/{export_id} until ready or completed; a failed result is not downloadable evidence.
For a verifier-adjudicated event-time slice, use occurred_at_from and occurred_at_to, as above. The chain time basis is governance_event.occurred_at. created_at_from and created_at_to filter runtime insertion time; those selectors are authenticated in canonical_filters.raw_filters, but they are not a v1 chain predicate. Their signed presentation policy records insertion_time_slice_completeness=unavailable. A SUPPORTED scope-faithfulness result applies only to the declared chain predicate; it does not establish completeness of an insertion-time slice or arbitrary runtime filters.
The important part is include_chain_entries=true. Without bundled chain entries, the verifier can still verify the export signature, but it cannot walk the event chain or verify permit closure evidence.
3. Download the proof pack
The proof pack is a ZIP with everything the verifier needs for one export. Download it from the dashboard or the API. Either way you get the same bytes.
From the dashboard: open Compliance → Exports, select a completed export, and choose Download proof pack (.zip). The Advanced: raw export files downloads (export payload only, manifest JSON only) are not a substitute; see below.
From the API: GET /v1/compliance/exports/{export_id} returns metadata, not the export bytes. Its file_path is a server location, not a customer download link. Use the authenticated proof-pack endpoint for the offline inputs:
curl --fail-with-body -sS \
-H "Authorization: Bearer $KEEL_ADMIN_API_KEY" \
"https://api.keelapi.com/v1/compliance/exports/$EXPORT_ID/proof-pack" \
-o proof-pack.zip
python -m zipfile -e proof-pack.zip proof-pack
cd proof-packPreserve the ZIP and every extracted entry. The proof pack contains evidence, not a verification verdict:
| File | Notes |
|---|---|
export.json | Exact signed export payload bytes for the JSON audit recipe above. |
manifest.json | Signed export manifest. |
key_manifest.json | Public signing-key material; verify its trust independently. |
checkpoint-scope-state-v1.json and checkpoint.json | Included when the export declares scope-faithfulness references; keep them beside the export and manifest. |
For the payload alone, use GET /v1/compliance/exports/$EXPORT_ID/download. That download does not replace the complete proof pack. An export payload and manifest without the scope-state sidecar and checkpoint fail verification. For an export that declares scope faithfulness, keel-verify exits 1 with export references an absent scope-state sidecar. The scope-state, scope-faithfulness, chain-continuity and closure claims then stay insufficient_evidence. A 409 export_scope_state_unavailable means referenced sidecars could not be assembled; preserve the refusal and request a new compatible export, rather than inventing or repairing files.
Keel authenticity requires the independently retrieved public key manifest or a pinned trusted key (including the bundled production root). A key embedded only in the artifact proves self-consistency, not signer identity.
Obtain the trust root independently
Do not take the signer’s identity from the same download as the evidence. Use at least one of these channels:
- Bundled production trust root (default). The
keel-verifierwheel you installed from PyPI in step 1 ships Keel’s production trust root. With no key flags,keel-verifychecks the export signing key against that root and performs no online key lookup. The report showsTrust mode: Keel production trust root. If a local~/.keel-verifier/trust-root.jsonexists, the verifier uses that file instead and reportsTrust mode: Pinned key, with the file named in the trust source. Use it only if you know where it came from; otherwise move it aside to verify against the bundled root. - Live public key manifest. Fetch
https://api.keelapi.com/v1/compliance/keysat verification time with--key-manifest-url, or download it yourself and pass the saved file with--key-manifest. This is a second channel to cross-check the bundled root against.
The proof pack’s key_manifest.json is signed and the verifier rejects it if its key material is edited. It still arrives with the evidence, so it is not an independent trust root by itself. See Three Trust-Root Channels for non-production instances and out-of-band roots.
4. Run the verifier
Run export verification, chain walking, and closure verification together.
Quick start (v3.5.0+): keel-verify export --export-file export.json --manifest manifest.json --walk-events --verify-closure — no key flags are needed for the bundled production trust root. Add --raw only for the legacy technical output.
From the extracted proof-pack directory, verify against the bundled production trust root. This performs no online key lookup.
keel-verify export \
--export-file export.json \
--manifest manifest.json \
--walk-events \
--verify-closureExit status 0 means every requested, required claim was supported. Any other exit status means it was not; read the report to see which claim failed.
To cross-check against the live public key manifest, fetch it at verification time.
keel-verify export \
--export-file export.json \
--manifest manifest.json \
--key-manifest-url https://api.keelapi.com/v1/compliance/keys \
--walk-events \
--verify-closureThe commands above use the installed public keel-verify CLI. Keep referenced sidecars in the same directory. A hosted export-integrity check checks stored export integrity and recorded summaries; it does not independently walk the event chain or verify closure. Use the standalone verifier for those claims.
5. Interpret the output
Check every requested claim. In 3.28.0 a requested claim is part of the
verdict: the run succeeds only when every requested, required claim (for
example --walk-events or --verify-closure) is supported. Still read each
requested section; missing, unsupported, failed, or unverifiable required
claims fail your review.
Bulk exports are refused when the chain contains raw action arguments that fail the export privacy guard. See When a bulk export is refused.
Some historical events with incompatible signed payloads can also cause refusal. Do not manually redact or repair signed chain entries. Exports can also be refused when a window exceeds bounded export limits. A window starting mid-chain cannot prove continuity before its first included event; missing closure or checkpoint artifacts cannot prove those claims.
With --raw, a successful run prints VERIFIED, then WALK-EVENTS: VERIFIED and VERIFY-CLOSURE: VERIFIED for each check you enabled; the samples below are --raw output. Without --raw the verifier prints its report instead. Confirm each requested section is present and VERIFIED.
VERIFIED
Export: export.json
Content hash: sha256:7f4a0e2d4f0a6c7c1a9c8a0c1e2b3d4f5a6b7c8d9e0f1234567890abcdef1234
Signature: ed25519:7n5e2i9Kq8nP4m1Zp0...
Public key: ed25519:BASE64_PUBLIC_KEY...
Key id: export-2026-annual
Trust source: key manifest (key_manifest.json) key_id=export-2026-annual status=active
WALK-EVENTS: chain_scope=project:6f38 first sequence_number=1028, prev_hash unverifiable from this bundle (continuity prior to window requires server-side verify_chain_integrity)
WALK-EVENTS: VERIFIED
chain_scopes: 1
entries_walked: 42
record_hash_checks: 42 PASS
prev_hash_checks: 41 PASS (excludes window-edge entries)
sequence_checks: 41 PASS
VERIFY-CLOSURE: VERIFIED
chain_scopes: 1
closures_verified: 9
signature_checks: 9 PASS
digest_checks: 18 PASS
dispatch_digest_check: PASSVERIFIED
Export: export.json
Content hash: sha256:7f4a0e2d4f0a6c7c1a9c8a0c1e2b3d4f5a6b7c8d9e0f1234567890abcdef1234
Signature: ed25519:7n5e2i9Kq8nP4m1Zp0...
Public key: ed25519:BASE64_PUBLIC_KEY...
Key id: export-2026-annual
Trust source: key manifest (key_manifest.json) key_id=export-2026-annual status=active
FAILED: WALK_RECORD_HASH_MISMATCH: event_id=evt_01JX2EXAMPLE expected=sha256:8b8d1b6c... actual=sha256:4c41a991...Read the output in layers:
VERIFIEDmeans the export bundle matched its signed export manifest.WALK-EVENTS: VERIFIEDmeans the bundled chain entries recomputed and linked correctly inside the exported window.VERIFY-CLOSURE: VERIFIEDmeans closure signatures, dispatch request body bytes digests, and provider/client digest references matched the bundled evidence.- Inspect the closure outcome as well:
client_digest_missingauthenticates the recorded absence of observed client-delivery evidence. Async worker completion or a later status poll does not prove that the original result was delivered to the client. FAILED: <CODE>:means the verifier found a tampering signal or an untrusted format. Use the Tampering Detection Matrix.
6. What to do if verification fails
Treat any verification failure as an audit-integrity incident until disproven.
- Preserve the original export bundle, export manifest, public key manifest, verifier version, command, stdout, and stderr.
- Re-run the same command from a clean directory without modifying the downloaded files.
- Request a fresh export for the same time window and compare the failure.
- Confirm that the key manifest came from
GET /v1/compliance/keysfor the same Keel environment. - Stop relying on the affected export in audit files until the failure is explained.
- Escalate to your Keel admin and to security@keelapi.com. Include the export ID, command, verifier version, error code, and file hashes.
- For incidents, request an
incident_evidenceexport that brackets the affected window with the closest available checkpoints before and after the window. A window after the newest checkpoint has no closing bracket until the next checkpoint is published.
Do not manually repair an export and then rely on the repaired copy. If the original fails verification, the original is the evidence.