Skip to Content
Running Keel Verify

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.0

Confirm that the CLI is available.

keel-verify --help

keel-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.json

The 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=true

For 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-pack

Preserve the ZIP and every extracted entry. The proof pack contains evidence, not a verification verdict:

FileNotes
export.jsonExact signed export payload bytes for the JSON audit recipe above.
manifest.jsonSigned export manifest.
key_manifest.jsonPublic signing-key material; verify its trust independently.
checkpoint-scope-state-v1.json and checkpoint.jsonIncluded 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-verifier wheel you installed from PyPI in step 1 ships Keel’s production trust root. With no key flags, keel-verify checks the export signing key against that root and performs no online key lookup. The report shows Trust mode: Keel production trust root. If a local ~/.keel-verifier/trust-root.json exists, the verifier uses that file instead and reports Trust 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/keys at 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-closure

Exit 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-closure

The 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
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: PASS
Tampered export
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
FAILED: WALK_RECORD_HASH_MISMATCH: event_id=evt_01JX2EXAMPLE expected=sha256:8b8d1b6c... actual=sha256:4c41a991...

Read the output in layers:

  • VERIFIED means the export bundle matched its signed export manifest.
  • WALK-EVENTS: VERIFIED means the bundled chain entries recomputed and linked correctly inside the exported window.
  • VERIFY-CLOSURE: VERIFIED means closure signatures, dispatch request body bytes digests, and provider/client digest references matched the bundled evidence.
  • Inspect the closure outcome as well: client_digest_missing authenticates 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.

  1. Preserve the original export bundle, export manifest, public key manifest, verifier version, command, stdout, and stderr.
  2. Re-run the same command from a clean directory without modifying the downloaded files.
  3. Request a fresh export for the same time window and compare the failure.
  4. Confirm that the key manifest came from GET /v1/compliance/keys for the same Keel environment.
  5. Stop relying on the affected export in audit files until the failure is explained.
  6. Escalate to your Keel admin and to security@keelapi.com. Include the export ID, command, verifier version, error code, and file hashes.
  7. For incidents, request an incident_evidence export 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.

Last updated on Edit this page on GitHub