GET /preflight/payment
Runs every check on a bank transfer, in one call, before your agent executes it:
- the IBAN itself — structural validity, country, bank code, bank name and BIC from the national registries, and fine-grained SEPA scheme reachability from the EPC register;
- the bank and its jurisdiction — sanctioned bank or BIC, embargoed, high-risk or monitored country (OFAC, EU, UN, FATF);
- the beneficiary name — screened against the OFAC SDN and Consolidated lists, the EU financial sanctions list and the UN Security Council list.
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.
x402 golden rule: the agent pays for the answer to its question. A
structurally broken IBAN is a useful answer, not an error: HTTP 200 with
a motivated stop. The 4xx range is reserved for requests the service cannot
answer at all.
The rule that makes the verdict worth reading
okis impossible while any block is short of coverage. A check that did not happen never passes.
This is what separates an honest composite from an optimistic aggregation. If
you submit no beneficiary name, the name was not screened — the answer is
insufficient_coverage, never ok, because an ok would read as a green light
on a beneficiary nobody looked at. Same when the bank cannot be resolved from
any registry covering the IBAN’s country: absence of a hit is not evidence of
clearance.
The verdict is always exactly the maximum severity among the reasons, and the reasons carry their block and their own severity — so you can recompute the verdict yourself from the blocks instead of taking it on trust.
One call in place of several
This endpoint is not priced at the catalog’s flat per-call rate, and that is
deliberate: one call runs several checks. It performs the work of the
/iban/resolve, /iban/screen and /screen/entity calls, in one round-trip
and one x402 settlement, and adds the part you would otherwise have to write
and maintain yourself — the decision table that turns three independent answers
into one verdict, with an explicit rule for what happens when a check could not
run.
No price is written on this page. The gateway’s
/catalog is the single source of truth for every amount,
including the unit endpoints above, so you can compare them there.
Scope and limits
Every response carries this sentence in data.limits, verbatim:
This is a control outcome, not a payment instruction: ‘stop’ and ‘review’ report what the checks found, and the decision to pay remains the caller’s. The beneficiary name is screened against designation lists only — the account holder’s name is never verified with the beneficiary bank (Verification of Payee under the EU Instant Payments Regulation is out of scope). Company identity, VAT status and e-invoicing capability are not covered.
In particular:
- This is not Verification of Payee. The beneficiary name is compared with designation lists, never with the account holder’s name held by the beneficiary bank. “Is this account really theirs?” is a different question, and this endpoint does not answer it.
- No company due diligence. Identity, insolvency, VAT and control chain are
the job of
GET /preflight/supplier. - No risk score. The verdict is a decision table over dated facts.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
iban | string | yes | The beneficiary IBAN, up to 64 characters; separators are tolerated |
beneficiary_name | string | no | Beneficiary name, 2 to 200 characters, screened exactly as GET /screen/entity does |
country | string | no | ISO 3166-1 alpha-2 filter on the beneficiary’s declared country, applied to the name screening only |
include_weak | bool | no | Whether weak name matches count towards the verdict; default false |
beneficiary_nameis optional, but omitting it forbidsok. Without it the name block isnot_requested, which is a coverage gap, not a pass.
country is your declaration, never an assertion of ours, and it can only
remove candidates whose country the list published in a comparable form — a
designation that publishes no country stays a candidate.
include_weak has the same semantics as on
GET /screen/entity: weak matches are returned
either way, the flag only decides whether they count towards the verdict. The
review example below becomes a stop with include_weak=true.
200 response — UnifiedResponse
{
"data": { ... },
"provenance": {
"source": "national-bank-registries+epc-sepa-registers+ofac-eu-un-sanctions+ofac-sdn+ofac-consolidated+eu-fsf+un-sc",
"fetched_at": "2026-07-30T19:06:01Z",
"freshness": { "kind": "snapshot", "as_of": "2026-06-11T00:00:00Z" }
}
}
The single provenance block carries the composite source and the
oldest of the dates involved — the honest freshness of the answer as a
whole. Each source is also listed with its own date in data.sources.
Fields of data
| Field | Type | Description |
|---|---|---|
query | object | The parameters as understood: iban, beneficiary_name, country, include_weak |
verdict | string | ok | review | stop | insufficient_coverage — the maximum severity among the reasons |
verdict_reasons | array | Every reason, most severe first: block, code, severity, detail. Empty on ok, and on ok only |
iban | object | The full /iban/resolve answer, unflattened |
bank_screening | object | coverage + the /iban/screen verdict, without its echo fields |
name_screening | object | coverage, lists_screened and the /screen/entity verdict |
limits | string | The fixed sentence above, served in every response |
sources | array | One dated entry per source actually consulted — the designation-list store appears only if a name was screened |
Each block keeps the exact shape of the matching standalone endpoint: nothing is flattened, nothing is summarised away. The risk band is not re-decided here — the composite reads the one risk table of the service.
Coverage of each block
| Field | Values |
|---|---|
iban.coverage | full (complete national registry) | partial (curated registry) | structure_only | null on an invalid IBAN |
bank_screening.coverage | screened | jurisdiction_only (embargoed country outside the IBAN registry) | not_applicable (the IBAN is invalid) |
name_screening.coverage | screened | not_requested (no name submitted — forbids ok) |
Verdicts
verdict | Meaning |
|---|---|
stop | Invalid IBAN, sanctioned bank or BIC, embargoed jurisdiction, or a name hit on the beneficiary |
review | Bank unknown in a complete national registry, high-risk or monitored jurisdiction (FATF, EU-AML), or a weak name match |
insufficient_coverage | No signal, but a check did not happen — no beneficiary name submitted, or a bank that could not be screened |
ok | Every check ran, and none of them raised a signal |
Reason codes
Ten codes, a closed set. Each reason carries the block it came from and its own severity.
code | Block | Severity |
|---|---|---|
IBAN_INVALID | iban | stop |
BANK_SANCTIONED | bank_screening | stop |
JURISDICTION_SANCTIONED | bank_screening | stop |
NAME_HIT | name_screening | stop |
BANK_UNKNOWN_IN_FULL_REGISTRY | iban | review |
JURISDICTION_HIGH_RISK | bank_screening | review |
JURISDICTION_MONITORED | bank_screening | review |
NAME_WEAK_HIT | name_screening | review |
BANK_SCREENING_INCOMPLETE | bank_screening | insufficient_coverage |
NAME_NOT_SCREENED | name_screening | insufficient_coverage |
Examples
All four examples below are captured from the production service, on the
snapshots dated in their own sources (bank registries 2026-07-25, EPC
register 2026-06-11, sanctions artefacts 2026-07-27, designation store
2026-07-28).
ok — every check ran, nothing raised a signal
GET /preflight/payment?iban=DE89370400440532013000&beneficiary_name=Compagnie%20de%20Saint-Gobain
{
"query": {
"iban": "DE89370400440532013000",
"beneficiary_name": "Compagnie de Saint-Gobain",
"country": null,
"include_weak": false
},
"verdict": "ok",
"verdict_reasons": [],
"iban": {
"iban": "DE89370400440532013000",
"valid": true,
"country": "DE",
"bank_code": "37040044",
"branch_code": null,
"bank": { "name": "Commerzbank", "bic": "COBADEFFXXX" },
"reachable": true,
"coverage": "full",
"sepa_reachability": {
"sct": true,
"sct_inst": true,
"sdd_core": true,
"sdd_b2b": true,
"schemes": ["SCT", "SCT_INST", "SDD_B2B", "SDD_CORE"],
"match": "exact"
},
"issue": null
},
"bank_screening": {
"coverage": "screened",
"screening": {
"risk": "clear",
"bank_sanctioned": false,
"jurisdiction": { "level": "clear", "lists": [] },
"hits": []
}
},
"name_screening": {
"coverage": "screened",
"screening": {
"query": {
"name": "Compagnie de Saint-Gobain",
"normalized": "compagnie de saint gobain"
},
"verdict": "no_hit",
"verdict_basis": [],
"hits": [],
"total_matches": 0,
"truncated": false
},
"lists_screened": [
{ "list": "EU-FSF", "entities": 6017 },
{ "list": "OFAC-CONS", "entities": 481 },
{ "list": "OFAC-SDN", "entities": 19157 },
{ "list": "UN", "entities": 1011 }
]
},
"limits": "This is a control outcome, not a payment instruction: …",
"sources": [
{ "source": "national-bank-registries", "as_of": "2026-07-25" },
{ "source": "epc-sepa-registers", "as_of": "2026-06-11" },
{ "source": "ofac-eu-un-sanctions", "as_of": "2026-07-27" },
{ "source": "ofac-sdn+ofac-consolidated+eu-fsf+un-sc", "as_of": "2026-07-28" }
]
}
ok here means something precise: the three blocks all reached full coverage —
full registry coverage on the IBAN, screened bank, screened name — and
none of them raised a reason. verdict_reasons is empty, and it is empty on
ok only.
stop — the beneficiary is on the lists
GET /preflight/payment?iban=DE89370400440532013000&beneficiary_name=Europ%C3%A4isch-Iranische%20Handelsbank
{
"verdict": "stop",
"verdict_reasons": [
{
"block": "name_screening",
"code": "NAME_HIT",
"severity": "stop",
"detail": "the beneficiary name matches a designated entity"
}
],
"iban": {
"valid": true,
"country": "DE",
"bank": { "name": "Commerzbank", "bic": "COBADEFFXXX" },
"reachable": true,
"coverage": "full"
},
"bank_screening": {
"coverage": "screened",
"screening": {
"risk": "clear",
"bank_sanctioned": false,
"jurisdiction": { "level": "clear", "lists": [] },
"hits": []
}
},
"name_screening": {
"coverage": "screened",
"screening": {
"verdict": "hit",
"verdict_basis": ["exact", "exact_alias"],
"hits": [
{
"id": "OFAC-SDN:25684",
"list_source": "OFAC-SDN",
"matched_name": "EUROPAISCH-IRANISCHE HANDELSBANK AG",
"match_type": "exact",
"programs": ["IFSR", "IRAN", "NPWMD"],
"listed_on": "2018-11-05",
"identifications": [
{ "type": "SWIFT/BIC", "number": "EIHBDEHH" }
],
"remarks": "All Offices Worldwide; (Linked To: BANK OF INDUSTRY AND MINE)"
},
{
"id": "EU:EU.13742.32",
"list_source": "EU-FSF",
"matched_name": "Europäisch-Iranische Handelsbank",
"match_type": "exact_alias",
"programs": ["IRN"],
"listed_on": "2025-09-29",
"remarks": null
}
],
"total_matches": 2,
"truncated": false
}
}
}
The account is perfectly valid and the bank perfectly clean — the transfer is
still a stop, on the beneficiary alone. The two blocks that passed are still
returned in full: the verdict never erases the evidence.
review — a weak match, decided by a human or a policy
GET /preflight/payment?iban=DE89370400440532013000&beneficiary_name=Melli%20Bank%20Investment%20Holding
{
"verdict": "review",
"verdict_reasons": [
{
"block": "name_screening",
"code": "NAME_WEAK_HIT",
"severity": "review",
"detail": "the beneficiary name matches a designated entity only weakly (source-flagged weak alias, or partial token coverage)"
}
],
"name_screening": {
"coverage": "screened",
"screening": {
"verdict": "weak_hit",
"verdict_basis": ["partial_tokens"],
"hits": [
{
"id": "EU:EU.13724.92",
"list_source": "EU-FSF",
"matched_name": "Bank Melli Iran Investment Company",
"match_type": "partial_tokens",
"programs": ["IRN"],
"listed_on": "2025-09-29"
},
{
"id": "OFAC-SDN:25421",
"list_source": "OFAC-SDN",
"matched_name": "BANK MELLI IRAN INVESTMENT COMPANY",
"match_type": "partial_tokens",
"programs": ["IFSR", "SDGT"],
"listed_on": "2018-11-05",
"remarks": "(Linked To: BANK MELLI IRAN)"
}
],
"total_matches": 2,
"truncated": false
}
}
}
The same call with include_weak=true returns the same two hits and the
verdict stop, on a NAME_HIT. The flag moves the line between review and
stop; it never hides a match.
insufficient_coverage — nothing bad, but nobody looked
GET /preflight/payment?iban=DE89370400440532013000
{
"query": {
"iban": "DE89370400440532013000",
"beneficiary_name": null,
"country": null,
"include_weak": false
},
"verdict": "insufficient_coverage",
"verdict_reasons": [
{
"block": "name_screening",
"code": "NAME_NOT_SCREENED",
"severity": "insufficient_coverage",
"detail": "no beneficiary name was submitted: nothing was screened against the designation lists"
}
],
"name_screening": {
"coverage": "not_requested",
"screening": null,
"lists_screened": []
},
"sources": [
{ "source": "national-bank-registries", "as_of": "2026-07-25" },
{ "source": "epc-sepa-registers", "as_of": "2026-06-11" },
{ "source": "ofac-eu-un-sanctions", "as_of": "2026-07-27" }
]
}
The IBAN resolved perfectly and the bank came back clean — the answer is still
not ok. Note lists_screened: [] and the missing designation store in
sources: we do not claim to have consulted what we did not read.
Other real cases, in one line each
| Call | Verdict | Reason |
|---|---|---|
iban=DE89370400440532013001 (one digit off) | stop | IBAN_INVALID, detail: "… (BAD_CHECKSUM)" — and bank_screening.coverage: "not_applicable", since there is nothing to screen |
iban=RU02044525600… (Russian IBAN) | stop | JURISDICTION_SANCTIONED, "the country of this IBAN is under embargo (OFAC-embargo, EU-AML)", risk: "prohibited" |
iban=DE44999999990532013000 (bank code not in the registry) | review | BANK_UNKNOWN_IN_FULL_REGISTRY — in a complete registry, an unknown code means the account is likely unreachable |
iban=TR33000610051978645784132… (country outside the registries) | insufficient_coverage | BANK_SCREENING_INCOMPLETE, and bank_screening.screening.risk: "unknown" — never read unknown as clear |
The invalid-IBAN case is worth a second look: one block is at
not_applicable coverage, yet the verdict is stop, not
insufficient_coverage. The verdict is the maximum severity, and a
structurally broken IBAN is a signal, not a gap.
Errors
| Status | code | Case |
|---|---|---|
| 400 | MISSING_PARAMETER | iban missing or empty |
| 400 | INVALID_INPUT | beneficiary_name outside 2–200 characters, or an invalid filter |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "code": "MISSING_PARAMETER", "error": "missing or empty required parameter: iban" }
A structurally invalid IBAN is never an error — it is a 200 with a motivated
stop.
See also
GET /preflight/supplier— the same idea on a counterparty: identity, insolvency, VAT, e-invoicing, control chain and sanctions, in one call.GET /iban/resolve,GET /iban/screenandGET /screen/entity— the three unit checks this endpoint runs, if you would rather orchestrate them yourself.- For agents — discovery surfaces, the live
/catalogand how settlement works.