POST /preflight/x402-seller
Checks an x402 payment challenge before your agent signs it - the act an x402 agent performs more often than any other, usually towards a seller it has never dealt with:
- screening - is the address the agent is about to credit published on a designation list (OFAC SDN and Consolidated, EU financial sanctions, UN Security Council)?
- price coherence - does the challenge ask for the amount this resource publishes in the public x402 Bazaar census, or for more?
- presence - is this resource in that census at all, and does it publish the address the challenge asks the agent to pay?
It returns one verdict - ok, review, stop or
insufficient_coverage - with every reason attached to the block it came
from, and each block’s full answer next to it. See the live
/catalog for the authoritative listing and price.
The input is declarative
Read this before anything else. The agent submits what the 402 challenge
served it, and the check runs on that:
- the challenge is never replayed - we do not call the seller’s resource;
- the seller’s origin is never contacted - no probe, no liveness ping;
- the answer is computed offline against dated data: a snapshot of the designation lists and a daily snapshot of the public x402 Bazaar census.
So this is not a live verification of the seller. What is checked is what your agent received, against lists and a census whose dates the response gives you. That is also why it answers in milliseconds and can be called before every signature.
Scope and limits
Every response carries this sentence in data.limits, verbatim:
This is a control outcome, not a payment instruction: ‘ok’ is not a recommendation to pay, and the decision remains the caller’s. The input is declarative — the x402 challenge is never replayed and the seller’s origin is never contacted. The pay-to address is screened against the designation lists and snapshot date reported in the screening block. Price coherence and liveness hold as of the census date reported in their blocks and never as of this call: the census lists a resource only after its first settlement, so an absent resource is a coverage gap and never a signal against the seller. This check does not attest that the seller will deliver, that the resource is safe, that the price is fair, or that the origin is reachable. No score, no rating, no reputation.
In plain terms, an ok does not attest that:
- the seller will deliver what it charges for;
- the resource is safe to call or its content trustworthy;
- the price is fair - only that it matches what the seller itself published;
- the origin is reachable right now.
And there is no score: the verdict is a decision table over dated facts.
The verdict - the maximum severity of the reasons
The verdict is exactly the maximum severity among verdict_reasons,
ordered insufficient_coverage < review < stop; no reason at all is
an ok, and ok only. Each reason names its block, its code and its own
severity, so you can recompute the verdict yourself from the reasons
instead of taking ours on trust. A coverage gap next to a review reads
review: the finding outranks the gap, and the gap is still listed.
| Block | Code | Severity | When |
|---|---|---|---|
screening | PAY_TO_SANCTIONED | stop | The pay-to address is published on a designation list |
screening | PAY_TO_NOT_SCREENABLE | insufficient_coverage | The address has no shape any screened family recognizes, and no list publishes it verbatim |
price_coherence | AMOUNT_ABOVE_PUBLISHED | review | The challenge asks for more than the resource published - the gap is in the detail |
price_coherence | AMOUNT_NOT_SUBMITTED | insufficient_coverage | No amount was sent: nothing was compared |
price_coherence | RAIL_NOT_PUBLISHED | insufficient_coverage | The resource publishes no rail for the declared network and asset |
price_coherence | RAILS_AMBIGUOUS | insufficient_coverage | Several candidate rails carry different assets and the request does not settle which |
price_coherence | AMOUNT_NOT_COMPARABLE | insufficient_coverage | The published amount is not an integer of atomic units |
presence | PAY_TO_NOT_PUBLISHED | review | The resource is in the census, but publishes other pay-to addresses than this one |
presence | RESOURCE_NOT_IN_SNAPSHOT | insufficient_coverage | The resource is not in the census |
presence | SNAPSHOT_STALE | insufficient_coverage | The census snapshot is older than 7 days (see below) |
Four rules worth stating plainly:
- Only a designated address is
stop. Nothing else in the table can produce it: not an absent resource, not an incomparable price, not a stale census. Those are coverage gaps. - An absent resource is never held against the seller. The census lists a
resource only after its first settlement, so a new seller is absent by
construction - on a market where almost everything is new. Absence is
insufficient_coverage, neverreviewand neverstop. - Only overcharging is a reason. A challenge asking for more than the
published amount is a
review, with the gap stated. A challenge asking for less is reported (comparison: "below_published") and motivates nothing - a price drop between the daily census and the call is the normal case. - Atomic units only compare within one asset. If the candidate rails carry
different assets (6 decimals here, 18 there) and the request does not say
which, the answer is
RAILS_AMBIGUOUS- never a “lowest amount” mixing magnitudes.
Two halves, dated separately
The two sources do not share a date, and the response never pretends they do:
- the screening block carries the date of the designation-list snapshot;
- the price coherence and presence blocks carry the date of the census snapshot.
Each block has its own freshness. The envelope’s provenance names both
sources and carries the oldest date - a composite answer is never fresher
than its oldest data.
Beyond 7 days, the census snapshot is not even consulted: price
coherence and presence both come back snapshot_stale, an
insufficient_coverage, rather than a comparison against data a week old.
The screening comes from the other store and keeps answering - including
stop.
Request body
Content-Type: application/json. Copy the challenge’s fields as they came:
| Field | Required | Description |
|---|---|---|
resource | yes | The resource URL exactly as the challenge named it - http(s), up to 2,048 characters |
pay_to (alias payTo) | yes | The address the challenge asks you to credit - a single on-chain address |
network | no | The challenge’s network (eip155:8453, or a legacy name such as base), up to 128 characters |
asset | no | The challenge’s asset (token contract or mint), up to 128 characters |
amount (alias maxAmountRequired) | no | The amount asked, as a decimal integer of atomic units - a JSON string or number |
{
"resource": "https://api.invoket.com/iban/screen",
"payTo": "0x4465825B0ac713aA2Fa07Ea29023020804A128ee",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"maxAmountRequired": "10000"
}
The challenge’s own field names are accepted as is. payTo and
maxAmountRequired are aliases of pay_to and amount: an agent copying
what its challenge served should not have to rename anything to check it.
Without an amount, the price is not compared and the verdict cannot be
ok (AMOUNT_NOT_SUBMITTED). Network names from both generations of the
protocol are reconciled through a closed, sourced table (base is
eip155:8453); a network token outside that table is never reinterpreted - it
simply matches no published rail, and the answer says so.
Address forms. The pay-to address is screened exactly as
/screen/address screens it, across the five
families it serves (Bitcoin, EVM, Tron, Solana, Monero). An address whose form
none of them recognizes - some Stellar or Algorand accounts, for instance - is
not refused: the screening block says unscreenable_address_form with an
empty lists_screened, and the verdict can never be ok. Refusing it would
deprive those sellers of any check; calling it not_listed would be a lie.
200 response - UnifiedResponse
| Field | Type | Description |
|---|---|---|
query | object | Your fields echoed, with resource_canonical - the normalized form the census was searched with |
verdict | string | ok, review, stop or insufficient_coverage |
verdict_reasons | object[] | { block, code, severity, detail } - one per finding; empty on ok |
screening | object | coverage (screened / unscreenable_address_form), freshness, the unit /screen/address answer as is, and lists_screened with each list’s address count |
price_coherence | object | coverage, freshness, declared_amount, published_amount, comparison (matches / above_published / below_published), difference, rails_matched, rails_published |
presence | object | coverage (listed / host_only / absent / snapshot_stale), freshness, host, host_resources, last_updated, usage, pay_to (published / not_published / unknown) |
limits | string | The fixed sentence quoted above |
lists_screened is what makes a not_listed opposable: it says which lists
were read and how many addresses each publishes. A list publishing zero
addresses is still named - the absence of a hit there is not evidence of
anything.
price_coherence.coverage is one of compared, snapshot_stale,
resource_not_in_snapshot, amount_not_submitted, rail_not_published,
rails_ambiguous or amount_not_comparable. A fact motivates the verdict
once, in the block it belongs to: an absent resource is a presence
reason, and price_coherence only says, in its coverage, that it had nothing
to compare.
Example - ok
POST /preflight/x402-seller with the challenge one of our own routes serves:
{
"data": {
"query": {
"resource": "https://api.invoket.com/iban/screen",
"resource_canonical": "https://api.invoket.com/iban/screen",
"pay_to": "0x4465825B0ac713aA2Fa07Ea29023020804A128ee",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "10000"
},
"verdict": "ok",
"verdict_reasons": [],
"screening": {
"coverage": "screened",
"freshness": { "source": "ofac-sdn+ofac-consolidated+eu-fsf+un-sc", "as_of": "2026-09-14" },
"screening": {
"query": {
"address": "0x4465825B0ac713aA2Fa07Ea29023020804A128ee",
"address_normalized": "0x4465825b0ac713aa2fa07ea29023020804a128ee",
"chain_family": "evm",
"chain_family_source": "inferred"
},
"verdict": "not_listed",
"hits": []
},
"lists_screened": [
{ "list": "EU-FSF", "addresses": 7 },
{ "list": "OFAC-CONS", "addresses": 0 },
{ "list": "OFAC-SDN", "addresses": 1064 },
{ "list": "UN", "addresses": 0 }
]
},
"price_coherence": {
"coverage": "compared",
"freshness": { "source": "cdp-x402-bazaar", "as_of": "2026-10-15" },
"declared_amount": "10000",
"published_amount": "10000",
"comparison": "matches",
"difference": "0",
"rails_matched": 1,
"rails_published": [
{
"scheme": "exact",
"network": "eip155:8453",
"pay_to": "0x4465825B0ac713aA2Fa07Ea29023020804A128ee",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "10000",
"matches_query": true
}
]
},
"presence": {
"coverage": "listed",
"freshness": { "source": "cdp-x402-bazaar", "as_of": "2026-10-15" },
"host": "api.invoket.com",
"host_resources": 13,
"last_updated": "2026-10-14T02:20:32.587Z",
"usage": {
"total_calls_30d": 4,
"unique_payers_30d": 3,
"last_called_at": "2026-10-14T02:20:31.539Z"
},
"pay_to": "published"
},
"limits": "This is a control outcome, not a payment instruction: 'ok' is not a recommendation to pay, and the decision remains the caller's. The input is declarative — the x402 challenge is never replayed and the seller's origin is never contacted. The pay-to address is screened against the designation lists and snapshot date reported in the screening block. Price coherence and liveness hold as of the census date reported in their blocks and never as of this call: the census lists a resource only after its first settlement, so an absent resource is a coverage gap and never a signal against the seller. This check does not attest that the seller will deliver, that the resource is safe, that the price is fair, or that the origin is reachable. No score, no rating, no reputation."
},
"provenance": {
"source": "ofac-sdn+ofac-consolidated+eu-fsf+un-sc+cdp-x402-bazaar",
"fetched_at": "2026-10-15T15:45:20.615133726Z",
"freshness": { "kind": "snapshot", "as_of": "2026-09-14T00:00:00Z" }
}
}
The two halves carry their own dates - designation lists as of 2026-09-14,
census as of 2026-10-15 - and the envelope carries the older one. The
usage figures are the census’s, served as published.
Example - review: the challenge asks for more
The same resource, with a challenge asking for 250000 atomic units
(blocks unchanged except price_coherence):
{
"data": {
"verdict": "review",
"verdict_reasons": [
{
"block": "price_coherence",
"code": "AMOUNT_ABOVE_PUBLISHED",
"severity": "review",
"detail": "the challenge asks for 250000 atomic units where this resource published 10000 at the census date: 240000 more"
}
],
"price_coherence": {
"coverage": "compared",
"freshness": { "source": "cdp-x402-bazaar", "as_of": "2026-10-15" },
"declared_amount": "250000",
"published_amount": "10000",
"comparison": "above_published",
"difference": "240000",
"rails_matched": 1,
"rails_published": [ "…" ]
},
"…": "…"
}
}
Twenty-five times the published price is exactly what this check exists to
catch before a signature. The reverse - a challenge for 10000 on
/iban/screen/batch, which publishes 15000 - is an ok with
comparison: "below_published" and difference: "5000": reported, not held
against anyone.
Example - stop: a designated address
A challenge that names a real resource but asks payment to an address on the OFAC SDN list - the shape of a spoofed or tampered challenge:
{
"data": {
"query": {
"resource": "https://api.invoket.com/iban/screen",
"resource_canonical": "https://api.invoket.com/iban/screen",
"pay_to": "0x0330070FD38Ec3bB94F58FA55D40368271E9e54A",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "10000"
},
"verdict": "stop",
"verdict_reasons": [
{
"block": "screening",
"code": "PAY_TO_SANCTIONED",
"severity": "stop",
"detail": "the pay-to address is published on a designation list (1 matching entry)"
},
{
"block": "presence",
"code": "PAY_TO_NOT_PUBLISHED",
"severity": "review",
"detail": "the pay-to address of this challenge is not among those this resource published at the census date"
}
],
"screening": {
"coverage": "screened",
"freshness": { "source": "ofac-sdn+ofac-consolidated+eu-fsf+un-sc", "as_of": "2026-09-14" },
"screening": {
"query": { "address": "0x0330070FD38Ec3bB94F58FA55D40368271E9e54A", "…": "…" },
"verdict": "sanctioned",
"hits": [
{
"entity_id": "OFAC-SDN:57056",
"entity_name": "AMNOKGANG TECHNOLOGY DEVELOPMENT COMPANY",
"list_source": "OFAC-SDN",
"programs": ["DPRK4"],
"listed_on": "2026-03-12",
"chain": "ETH",
"address_published": "0x0330070FD38Ec3bB94F58FA55D40368271E9e54A"
}
]
},
"lists_screened": [ "…" ]
},
"presence": { "coverage": "listed", "pay_to": "not_published", "…": "…" },
"…": "…"
}
}
Two reasons, two severities, one verdict: the maximum, stop. The review
from presence is still served - the resource exists and publishes a
different address, which is the second thing the agent should know.
Example - insufficient_coverage: a resource never settled
A challenge for one of our routes that nobody had settled yet at the census
date (screening as in the ok example):
{
"data": {
"query": {
"resource": "https://api.invoket.com/company/lookup",
"resource_canonical": "https://api.invoket.com/company/lookup",
"pay_to": "0x4465825B0ac713aA2Fa07Ea29023020804A128ee",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "10000"
},
"verdict": "insufficient_coverage",
"verdict_reasons": [
{
"block": "presence",
"code": "RESOURCE_NOT_IN_SNAPSHOT",
"severity": "insufficient_coverage",
"detail": "this resource is not in the census, which lists 13 other resource(s) on the same host: the census only lists a resource after its first settlement"
}
],
"price_coherence": {
"coverage": "resource_not_in_snapshot",
"freshness": { "source": "cdp-x402-bazaar", "as_of": "2026-10-15" },
"declared_amount": "10000",
"published_amount": null,
"comparison": null,
"difference": null,
"rails_matched": 0,
"rails_published": []
},
"presence": {
"coverage": "host_only",
"freshness": { "source": "cdp-x402-bazaar", "as_of": "2026-10-15" },
"host": "api.invoket.com",
"host_resources": 13,
"last_updated": null,
"usage": null,
"pay_to": "unknown"
},
"…": "…"
}
}
The address was screened (not_listed), but the price could not be compared
and presence could not be confirmed - so not ok. The host is known, the
resource is not yet: the census lists a resource only after its first
settlement, and the answer says so instead of turning novelty into a signal.
The absent resource motivates the verdict once, in presence.
Errors
Only requests the service cannot answer leave the 200 range - none of them are billed.
| Status | code | Case |
|---|---|---|
| 400 | MISSING_PARAMETER | resource or pay_to absent or empty |
| 400 | INVALID_INPUT | resource longer than 2,048 characters or not http(s) (its content is not echoed back); pay_to containing a space; network or asset longer than 128 characters; amount that is not a decimal integer of atomic units |
| 400 | UNRECOGNIZED_ADDRESS_FORMAT | pay_to that has no address shape at all |
| 400 | INVALID_BODY | Body that is not valid JSON |
| 405 | METHOD_NOT_ALLOWED | Any method other than POST |
| 503 | DATA_UNAVAILABLE | The designation-list store or the census is not loaded: two blocks out of three at full price would be a silent degradation |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "code": "INVALID_INPUT", "error": "parameter 'resource' must be the http(s) URL of the paid resource, as served by the 402 challenge" }
{ "code": "INVALID_INPUT", "error": "parameter 'amount' must be a decimal integer of atomic units, as published by the 402 challenge" }
Attribution
Designation data comes from the OFAC SDN and Consolidated lists (US
Treasury, public domain), the EU financial sanctions list (European
Commission) and the UN Security Council consolidated list. Census data
comes from the public x402 Bazaar discovery index of the Coinbase
Developer Platform (cdp-x402-bazaar), as published at the snapshot date.
See also
GET /screen/address- the unit screening this pre-flight runs on the pay-to address, unchanged.GET /preflight/payment- the same kind of verdict before a bank transfer.GET /preflight/supplier- the same kind of verdict on a company before paying it.- For agents - discovery surfaces, the live
/catalogand how settlement works.