Sealed artifact endpoints

Six endpoints, one per outcome state, each returning the sealed artifact for a run in that state. The five receipts are checked offline with verdifax-verify; an allowed run is checked through its full record.

See Outcome states & sealed artifacts for the conceptual overview and field-level schema.

Endpoints

GET /runs/{id}/allow-token               → AllowToken            (PEPG admit + nine-stage pipeline)
GET /runs/{id}/deny-receipt              → DenyReceipt           (PEPG)
GET /runs/{id}/ccv-halt-receipt          → CCVHaltReceipt        (CCV)
GET /runs/{id}/macc-halt-receipt         → MACCHaltReceipt       (MACC)
GET /runs/{id}/aivp-t4-halt-receipt      → AivpT4HaltReceipt     (AIVP-T4)
GET /runs/{id}/attestation-gap-receipt   → AttestationGapReceipt (admission)

Each endpoint returns the sealed artifact for run {id} if and only if the run resolved to the matching outcome state. Otherwise it returns 404 Not Found with a clear error explaining which state the run is in.

How to pick the right endpoint: Use the outcome_kind field from the run summaries (GET /runs or GET /runs/search); GET /runs/{id} carries the same value as status:

  • ok → /runs/{id}/allow-token
  • pepg_deny → /runs/{id}/deny-receipt
  • ccv_halt → /runs/{id}/ccv-halt-receipt
  • macc_halt → /runs/{id}/macc-halt-receipt
  • aivp_t4_halt → /runs/{id}/aivp-t4-halt-receipt
  • attestation_gap → /runs/{id}/attestation-gap-receipt
  • stage_error → No artifact endpoint exists (run record only)

Authentication

Same as every other run endpoint, pass X-Verdifax-Key: $VERDIFAX_KEY. The orchestrator scopes results to the API key that authored the run, so one customer cannot retrieve another customer's artifacts.

Response shape

All six return the same envelope:

{
  "ok": true,
  "run_id": 42,
  "<artifact_key>": { ... },
  "receipt_anchor": { ... }
}

Where <artifact_key> is one of:

  • allow_token
  • deny_receipt
  • ccv_halt_receipt
  • macc_halt_receipt
  • aivp_t4_halt_receipt
  • attestation_gap_receipt

The five receipt responses carry a sibling receipt_anchor (the receipt's public-log entry) when the receipt was anchored; receipts issued before run 241 have none. The allow_token response has no receipt_anchor.

The artifact object itself is a canonical-bytes record (RFC 8785 + SHA-256) whose fields are documented in Outcome states. The terminal hash field is the SHA-256 of the canonical preimage and is the value verdifax-verify recomputes. Since verifier 0.13.0, verdifax-verify checks the receipt document the API serves, including the receipt's public-log entry; receipts issued before run 241, at 23:11 UTC on 2026-10-16, when receipt anchoring started, carry no entry and are reported as self-consistent only. Receipts anchored under receipt anchor v2 also bind the run number and carry anchor_required: true, which verifier 0.14.0 and later releases read; earlier releases report such receipts as FAILED.

Example, fetching a DenyReceipt

curl -H "X-Verdifax-Key: $VERDIFAX_KEY" \
  https://api.verdifax.com/runs/42/deny-receipt
{
  "ok": true,
  "run_id": 42,
  "deny_receipt": {
    "anchor_required": true,
    "envelope_id": "env-7a3c…",
    "fired_rule_id": "mcd-llm-001",
    "decision_reason_code": "MCD_SIGNATURE_MATCH",
    "policy_hash": "8f1d…",
    "evaluation_hash": "b2e0…",
    "mcd_finding_hash": "41c6…",
    "deny_clock": "2026-04-30T17:23:48.124Z",
    "evaluator_version": "pepg-0.3.0",
    "version": "vfa.deny_receipt.v1",
    "hash": "9e7f…"
  },
  "receipt_anchor": { ... }
}

The hosted API's built-in policy admits every request, so its only deny is a Malicious Code Defense signature match: fired_rule_id is the matched signature id and mcd_finding_hash is populated. Receipts sealed by the hosted API since run 242 carry "anchor_required": true.

Independent verification

The audit PDF gives a copy-pasteable command for each run. For the five receipts, save the document and verify it; only the path changes:

curl -H "X-Verdifax-Key: $VERDIFAX_KEY" \
  https://api.verdifax.com/runs/42/deny-receipt -o receipt.json
verdifax-verify receipt.json

verdifax-verify recomputes the receipt's hash from its own fields and checks its public-log entry offline: the inclusion proof, the log's signed timestamp, that a published Verdifax log-signing key logged it, and that it was logged within 600 seconds of the receipt's own clock. Exit code 0 means RECEIPT VERIFIED AND ANCHORED. A receipt issued before receipt anchoring began (run 241) carries no entry and reads RECEIPT SELF-CONSISTENT ONLY, NOT ANCHORED, exit code 1 unless --allow-unanchored is passed. Exit code 1 with FAILED means a check failed, and exit code 2 means the input could not be parsed.

An AllowToken is not checked on its own: it is sealed inside the allowed run's full record, so verify that record instead (save GET /runs/{id}/artifacts as bundle.json, then run verdifax-verify bundle.json).

When you'll see 404

{ "ok": false, "error": "run 42 is \"ok\", not \"pepg_deny\", no deny_receipt exists" }

The error message names both the actual outcome state of the run and the state required for the requested artifact. The dashboard handles this automatically by inspecting outcome_kind on the run summary before choosing which endpoint to call.

Outcome discriminator on run summaries

Every entry in GET /runs and GET /runs/search carries an outcome_kind field with one of: ok, pepg_deny, ccv_halt, macc_halt, aivp_t4_halt, attestation_gap, stage_error. Use it to branch on which artifact to fetch without parsing the manifest.