Pre-flight: check an x402 endpoint before you pay it
Eighteen conformance checks on any x402 endpoint without settling anything: live 402 challenge, decoded payment terms, and whether the price it advertises matches the price it charges.
The problem
An agent about to buy from an x402 endpoint it has never used knows almost nothing about it. Is the endpoint live? Does the challenge decode? Is the price in its published spec the price it will actually take from your wallet? Directories publish uptime, and uptime tells you the server answered. It does not tell you the terms are honest or that the resource is configured in a way a client can pay.
GET /v1/preflight?url=<endpoint> ($0.005) answers that. jurat probes the endpoint unpaid, reads its published spec, and returns a verdict, the decoded payment terms and a fix for anything wrong. Nothing is settled against the endpoint being checked.
The headline check: does the advertised price match the charged price
x402 carries the price twice, in two different units. The spec surface (x-payment-info.price.amount in openapi.json) is decimal USD to six places, so a half-cent is "0.005000". The live 402 challenge carries atomic units, so on USDC that same half-cent is "5000". They are written by different code paths, and when they drift, nothing complains: a 1000x mispricing looks exactly like a correct one from the outside. Pre-flight decodes the live challenge, converts atomic to USD using the decimals of the settlement asset, and compares. If the asset is one it does not recognise, it reports the check as skipped rather than guessing at decimals.
What gets checked
| check | what a failure means |
|---|---|
reachable | the endpoint did not answer at all |
challenge_402 | an unpaid request got something other than 402, so no client can start the payment flow |
bare_probe_402 | the challenge only appears once parameters are supplied, so bare discovery probes see a 400 instead |
challenge_decodes | a 402 came back with no parseable x402 challenge in it |
head_challenge | HEAD does not return the challenge; discovery monitors probe with HEAD and silently drop resources that answer anything else |
payto_present | no valid payTo address, so settlement is impossible |
asset_known | unrecognised settlement asset, so the price cannot be independently checked |
amount_parses | the amount is not a number that converts to a price |
scheme_exact | a scheme most clients do not implement |
resource_url_matches | the challenge advertises a different URL than the one called, usually http:// behind a TLS terminator |
openapi_present | no openapi.json, the canonical discovery surface |
openapi_declares_path | the endpoint is missing from its own spec |
openapi_output_schema | no 200 response schema; AgentCash drops operations without one |
openapi_input_declared | no declared parameters, so probers send bare requests and classify the endpoint as broken |
openapi_payment_info | paid operation with no declared price |
price_agreement | the spec price and the live 402 price disagree |
x402_manifest | no /.well-known/x402.json for fallback discovery |
error_surface_json | unknown paths return HTML, which a JSON client cannot parse |
Response
{
"target": "https://zaref.dev/v1/repo-prime",
"origin": "https://zaref.dev",
"checked_at": "2026-07-25T11:40:02.881Z",
"verdict": "pass",
"is_paid_resource": true,
"safe_to_pay": true,
"summary": { "checks": 18, "passed": 17, "warnings": 0, "failures": 0, "skipped": 1 },
"live_terms": {
"scheme": "exact",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"pay_to": "0x...",
"amount_atomic": "5000",
"price_usd": "0.005000"
},
"checks": [
{ "check": "price_agreement", "status": "pass",
"detail": "spec and live 402 both say $0.005000" }
]
}
Every check carries a status of pass, warn, fail or skip, and a failing or warning check carries a fix saying what to change. verdict is the worst thing seen: pass, warn, inconclusive when the terms could not be reached at all, or fail.
safe_to_pay is the field to branch on and it has three values. true means nothing found would stop a payment working or make the price wrong, so a warn is still payable. false means something would. null means pre-flight could not read the terms, so neither answer would be honest.
Endpoints that validate before the paywall
Some services check parameters before returning the challenge, so an agent never pays to learn it made a typo. A bare probe of one of those gets a free 400 and never sees a 402. Pre-flight does not call that broken. When the first probe is rejected, it reads the parameter examples out of the endpoint's own openapi.json, builds a valid request and probes again. Specs that declare no example but spell one out in the prose ("Example: /v1/id-validate?id=8001015009087") are read too, the way a human would. If the challenge appears, the endpoint passes, with bare_probe_402 raised as a warning because bare discovery probes still see the 400 and some indexes will drop a resource over it. If no valid request can be built at all, the verdict is inconclusive and safe_to_pay is null: the endpoint is probably payable, pre-flight just could not prove it.
Free endpoints
An operation that declares security: [] and no 402 response is a free endpoint. Pre-flight recognises it, reports is_paid_resource: false and skips every payment check, instead of calling a service broken for not charging.
What it does not claim
Pre-flight reports what jurat observed at checked_at. A pass says the endpoint's terms are coherent and its discovery surface is sound. It is not a promise the seller will deliver, not an audit of the data behind the endpoint, and not an affiliation with it. Checking is done over public HTTP with the same guardrails as every other jurat fetch: public addresses only, capped bodies, short timeouts, and juratbot in the user agent.