Published 2026-08-31. Binding specification, frozen after publication.
Superseded by preflight_v2 at a version boundary announced 2026-09-01. v1 fixtures remain for the audit trail.
This document is the binding specification for the composite preflight envelope, schema version preflight_v1. It is frozen after publication. Changes happen only at an announced version boundary with a new schema_version.
The endpoint that returns these envelopes is not built yet. This is the schema and a set of locked, really signed fixtures, published so an integrator can build against a fixed target before anything is paid. When the endpoint ships, the price is 0.015 dollars per call.
One call returns three independent checks of a single subject address, each as its own signed sub result: payability, sanctions, and trust. Nothing collapses into a single score. The envelope carries a components_evaluated count and that is the only top level tally.
The three checks run sequentially within one request, in a fixed order: payability, then sanctions, then trust. All three always run. The envelope records the observation mode and the spread in milliseconds between the first and last observation, so the small time gap is visible rather than hidden.
schema_version: the constant "preflight_v1".request: request_id, request_time in RFC3339, and subject with address, chain in CAIP-2 form, and role which is one of payer, payee, escrow_destination.components: exactly three component objects, in the order payability, sanctions, trust.observation: mode is the constant "sequential-within-one-request", and spread_ms is the integer millisecond spread across the three observations.components_evaluated: an integer from 0 to 3, the count of components whose verdict is not not_evaluated. There is no aggregate verdict and no score at the top level.envelope: signer and signature.All three components share one structure: component, verdict, reason_codes (a non empty array; not_evaluated always carries at least one), observed_at, freshness_bound_seconds, limitations (always present), subject_echo, signer, manifest with url and entry, and component_digest and component_signature so each component verifies on its own, offline.
Verdict vocabulary and freshness bound, per component:
The list is locked at this version. Where a source service already emits a code, we reuse its literal rather than invent a new one. New codes require a version boundary.
Reused from the source services. payability, from the address preflight transfer simulation and option verdicts: simulated_ok, simulated_revert, EVM_NO_ATA_CHECK_NEEDED, ATA_MISSING_NEVER_EXISTED, ATA_CLOSED_AFTER_USE, NOT_SPL_SETTLEMENT. trust, from the reputation scorer status: insufficient_data.
Derived, because those services expose no reason code taxonomy of their own. sanctions: ofac_sdn_match, ofac_sdn_no_match. trust: erc8004_scored.
Parameterized codes attach a value after a colon: revert_reason:<reason string>, matched_label:<OFAC chain label>.
Operational, shared across all components: oracle_unreachable, rpc_timeout, invalid_subject.
The envelope is always complete and signed. Every component is present and signed even when its verdict is not_evaluated. There is never a partially signed envelope.
Total refusal happens in exactly two cases, and only before any component runs:
In every other case the caller gets a full signed envelope, with not_evaluated components where a check could not complete.
Signer: 0x57fF0F084Cba33e6761503f90eEF0Da9F159350c, published in the proof manifest. Signing is Ethereum EIP-191 personal_sign over canonical JSON, the same discipline as our sanctions screen: json.dumps with sort_keys true and separators of a comma and a colon, which escapes non ASCII as backslash u sequences.
To verify a single component offline:
To verify the whole envelope:
A component verifies even if you discard the rest of the envelope. Store the full body plus a manifest snapshot for long term audit, and verification keeps working after a signer rotation, because the manifest keeps a rotated signer alongside the new one with a validity boundary.
Every verdict is point in time evidence at its observed_at, bounded by its freshness_bound_seconds. This is not a guarantee of future settlement. A payable destination can stop being payable, an OFAC snapshot can change, and reputation data can move. Read the freshness bound before relying on a verdict.
This is the clean-pass fixture, our own payee address, all three checks really run and really signed. The full fixtures are published byte for byte with real signatures.
{
"schema_version": "preflight_v1",
"request": {
"request_id": "pf_6527a6b060353100",
"request_time": "2026-08-31T16:30:00Z",
"subject": { "address": "0xc87a06DEE4c0E85912296002617120BBfd5EF990", "chain": "eip155:8453", "role": "payee" }
},
"components": [
{ "component": "payability", "verdict": "PAYABLE", "reason_codes": ["simulated_ok"], "observed_at": "2026-08-31T16:30:00Z", "freshness_bound_seconds": 300, "limitations": ["..."], "signer": "0x57fF0F...350c", "manifest": {"url":"/proof/index.json","entry":"services[7] payable-address"}, "component_digest": "...", "component_signature": "0x..." },
{ "component": "sanctions", "verdict": "CLEAR", "reason_codes": ["ofac_sdn_no_match"], "freshness_bound_seconds": 86400, "...": "..." },
{ "component": "trust", "verdict": "NO_DATA", "reason_codes": ["insufficient_data"], "freshness_bound_seconds": 3600, "...": "..." }
],
"observation": { "mode": "sequential-within-one-request", "spread_ms": 12 },
"components_evaluated": 3,
"envelope": { "signer": "0x57fF0F...350c", "signature": "0x..." }
}
clean-pass is our payee address: payable, clear, no trust data. sanctions-match is a live OFAC SDN address: an OFAC match, and its transfer simulation also reverts. partial-not-evaluated shows a real not_evaluated trust component; the live trust oracle returns NO_DATA for an address with no ERC-8004 registration, so that one component is marked synthetic_condition true with a one line note, because a genuine oracle outage could not be forced honestly from outside. invalid-subject is the real HTTP 400 refusal body, no envelope.
The paid /payable and /screen endpoints are unchanged by this document. This composite lands there only at an announced version boundary. Signed report manifest at /proof/index.json.