Troubleshooting
Common issues when integrating with Verdifax, with the resolution steps that close them. Organized by error symptom so you can find the right page from a stack trace or a curl response.
Authentication
{"ok": false, "error": "missing X-Verdifax-Key header"}
You called an authenticated endpoint without an API key. Three fixes:
-
Set the header, Verdifax uses
X-Verdifax-Key, not the commonAuthorization: Bearer ...pattern.curl -H "X-Verdifax-Key: $VERDIFAX_ADMIN_KEY" \ https://api.verdifax.com/runs -
Get a key if you don't have one. Keys for the hosted API at
https://api.verdifax.comare issued after an access request at verdifax.com/request;POST /admin/keysthere requires an admin key and returns401without one. On a self-hosted deployment (which needs access to the private image) where no key has ever been created (open mode), the firstPOST /admin/keysis accepted without auth and returns the secret once. Mint that first key with admin scope; a key minted without it is customer scope and gets403on/admin/*:curl -X POST http://localhost:9090/admin/keys \ -H "Content-Type: application/json" \ -d '{"name": "ops-admin", "scope": "admin", "confirm_admin_scope": true}'Once any key exists, revoked or not,
POST /admin/keysrequires an admin key. If every admin key is lost or revoked, run the break-glassverdifax-api --mint-admin-key <name>inside the machine that hosts the API server. -
Persist the secret,
POST /admin/keysreturns the secret exactly once. After that, it's hashed in the orchestrator's database and not retrievable. If you lost it, revoke and create a new one.
401 Unauthorized or 403 admin scope required on /admin/erase or /admin/keys/{id}
Admin endpoints, including /admin/erase and /admin/keys, require
an admin-scope API key. A valid customer-grade key gets
403 admin scope required. If you got a 401, the key you sent is
either revoked, malformed, or for a different deployment. Re-check
via GET /admin/keys with an admin key (it lists every key without
secrets).
Manifest verification
verified: false with non-empty recomputed_hash
The manifest hash recomputed from the sealed fields doesn't match what was stored. Two possible causes:
-
The run was sealed under an older formula era. Verify the
formula_versionfield in the response. If it saysv1.6 (Phase 16, Kernel Artifacts)but your verifier expectsv1.7 (Phase 17, CRES), the run pre-dates CRES (the current era isv1.9 (execution record)). All eras verify under the same orchestrator binary; the dispatch picks the right formula based on which JSON keys are present in the sealed manifest. If your local verifier hard-codes an era, update it to use the dispatch table. -
Tampering or storage corruption. The hash is recomputed from the parsed values of the sealed manifest fields, so JSON formatting (whitespace, key order) does not affect it. If any sealed field's value differs from what Verdifax sealed, even by one character, the hash mismatches. This is the intended invariant; treat it as evidence the artifact has been modified.
verified: true but the audit PDF still complains
The PDF and the JSON manifest are independent surfaces. If the PDF
shows a section as (not declared) but the JSON has the field
populated, the issue is in the PDF rendering layer, not the
sealed data. Re-render the PDF via
GET /runs/{id}/report.pdf?format=comprehensive and the section
should populate from the live JSON.
Audit PDFs
404 Not Found on /runs/{id}/report.pdf
The endpoint is /runs/{id}/report.pdf, not /runs/{id}/audit.pdf
or /runs/{id}/audit. Older Verdifax deployments used
/audit; that path was renamed to /report for clarity.
PDF renders but section X is empty
Audit PDFs degrade gracefully, if the run didn't reach a stage,
the section shows (not declared) and the manifest hash still
seals. This is the intended behavior; absence is informational,
not an error.
If a stage should have populated a section but didn't, check the
run's status field via GET /runs/{id}:
pepg_deny, PEPG denied the request before downstream stages ran. Check the DenyReceipt at/runs/{id}/deny-receipt.ccv_halt, per-run budget breached at the post-DKEC check. Check the CCVHaltReceipt at/runs/{id}/ccv-halt-receipt.macc_halt, the budget bucket's cumulative budget breached. Check the MACCHaltReceipt at/runs/{id}/macc-halt-receipt.stage_error, a stage hit an error. Check the run'serror_stageanderror_messagefields.
CRES (cryptographic record erasure)
POST /admin/erase is admin-only. It destroys a per-field
data-encryption key and writes a sealed deletion receipt. The
encryption path is not yet wired into /execute, so the destroyed key
encrypts no stored field and the run's sealed bundle is unchanged. A
deletion receipt therefore records that a key was destroyed; it is not
evidence that any readable content was removed.
POST /admin/erase returns 400 ciphertext_hash must be 64-char SHA-256 hex
You passed a malformed ciphertext_hash. Three checks:
- The string is exactly 64 characters.
- It contains only
0-9anda-f(lowercase only). - It's not the plaintext, the orchestrator wants the SHA-256 of the ciphertext, which the caller should have recorded at encryption time.
POST /admin/erase returns 404 run not found on a valid run_id
The endpoint is admin-only, and it also looks the run up scoped to
the API key that's calling it. If you're using an admin key that
didn't create the run, you'll get 404 instead of 403
(defense-in-depth: you can't enumerate runs you don't own). A
customer-grade key gets 403 admin scope required before the lookup.
Receipt verifies offline but the orchestrator says dek_still_present
Two surfaces report different things:
- Recomputing the receipt hash from its canonical preimage (the fields and joining rule the audit PDF lists) confirms only that the receipt is self-consistent. It can't see the orchestrator's keystore. The open-source verifier does not check deletion receipts.
- The orchestrator's
Verify()method also checks that the DEK is gone from the keystore. If it reportsdek_still_presentbut the receipt is otherwise valid, the shred operation either failed or was reverted by a backup-restore. Re-issue the shred.
Sigstore Rekor anchoring
LedgerProofHash is empty in the manifest
You're running with VERDIFAX_LEDGER_MODE=mock (the default for
local dev + tests). To get real Rekor anchoring, set
VERDIFAX_LEDGER_MODE=rekor on the orchestrator and restart.
Rekor link in PDF returns Search index does not exist
Rekor's search UI takes ~30 seconds to surface a newly anchored
entry. A run whose PDF shows a Rekor link was anchored: a failed
Rekor submission does not produce an unanchored OK run, it fails the
run at stage LEDGER, which is stored with status stage_error.
The orchestrator logs such a failure as "pipeline rejected" with
stage LEDGER and an error of the form
orchestrator: stage LEDGER failed: rekor: .... Most of those
failures are transient, and the next run anchors successfully.
Performance
/execute is slow when VERDIFAX_AIVP_MODE=t4
Real-mode AIVP invokes the live Anthropic Claude API per
request. Per-call latency is dominated by Claude's response time
(~50ms-500ms p50). Switch to VERDIFAX_AIVP_MODE=mock for tests
and local development; reserve t4 for production governance.
Orchestrator OOM at 256MB
Default Fly machine is shared-cpu-1x with 256MB. If you're seeing
OOM kills under load, bump to shared-cpu-1x 512MB by editing the
[[vm]] block in fly.toml:
[[vm]]
size = "shared-cpu-1x"
memory = "512mb"
Per-run memory is dominated by the AIVP subprocess (~10MB resident) plus PDF rendering (peaks at ~50MB during comprehensive format generation). 256MB is enough for steady-state ~50 req/sec; above that, bump.
Where to file unresolved issues
If your problem isn't covered here:
- GitHub Issues, for bugs in the open-source verifier CLI: https://github.com/Verdifax/verdifax-verify/issues. The orchestrator source repository is not public, so it has no public issue tracker.
- Audit access, for buyers needing run-specific support: audit-access@verdifax.com
- Security disclosures, see SECURITY-TRUST.
