IBAN & EU VAT Validator: VIES, Peppol, Invoice Fraud Check
Pricing
from $1.00 / 1,000 payee checks
IBAN & EU VAT Validator: VIES, Peppol, Invoice Fraud Check
Validate IBANs and EU VAT numbers (live VIES) and catch bank-change invoice fraud before you pay a supplier. Adds Peppol lookup, invoice VAT maths and SEPA date. ALLOW / REVIEW / BLOCK verdict with reasons, for AP automation and AI payment agents.
Pricing
from $1.00 / 1,000 payee checks
Rating
0.0
(0)
Developer
Lerider Technologies
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
14 hours ago
Last modified
Categories
Share
Validate IBANs and EU VAT numbers (live VIES) and catch bank-change invoice fraud before you pay a supplier.
Built for accounts-payable automation and AI payment agents. Each check returns one verdict, ALLOW, REVIEW or
BLOCK, together with the reasons for it.
Typical uses: IBAN validation, EU VAT number validation (VIES), Peppol participant lookup, supplier verification before onboarding, and spotting business email compromise (BEC) where a known supplier suddenly sends a new IBAN.
| Event | Price | What it does |
|---|---|---|
payee-check | $0.001 | Offline IBAN check: checksum, Dutch bank code, a known payee asking for a new IBAN, invoice IBAN mismatch, pressure and bank-change language |
supplier-preflight | $0.03 | Everything payee-check does, plus a live EU VAT id check (VIES), a Peppol Directory lookup, invoice VAT arithmetic with reverse-charge checks, and the SEPA settlement date |
attested-check | $0.02 | Optional add-on ("attest": true) for either check: a timestamped evidence record of the input, verdict and rule version, so you can later prove the check was done before you paid |
The input picks the event. A request with iban or payments runs payee-check. A request with payee or
preflights runs supplier-preflight. Don't mix the two in one run. Add "attest": true to either one to get
an attested verdict.
Supplier pre-flight: "before I pay or onboard this supplier, is anything wrong?"
Input
Only payee.iban is required. Every other field you add switches on more checks.
{"payee": {"name": "Jansen Bouw B.V.", "iban": "NL91ABNA0417164300","vat_id": "NL123456789B01", "peppol_id": "0106:12345678"},"invoice": {"text": "Factuur 2026-118 Jansen Bouw B.V., btw-id NL123456789B01. Totaal EUR 121,00. IBAN NL91 ABNA 0417 1643 00.","lines": [{"net": 100.0, "vat_rate": 21}],"total_net": 100.0, "total_vat": 21.0, "total_gross": 121.0,"buyer_vat_id": "BE0123456789", "issue_date": "2026-10-20"},"known_payees": [{"name": "Jansen Bouw BV", "iban": "NL02RABO0123456789"}],"pay_on": "2026-12-25"}
- Amounts can be JSON numbers or decimal strings, with at most 2 decimals. Negative amounts are allowed for credit
notes.
vat_rateis a percentage. Lines may also carry their ownvatamount, which is then checked too. peppol_idisICD:identifier, for example0106:12345678(Dutch KvK),0208:0123456789(Belgian enterprise number) or9944:NL123456789B01. Theiso6523-actorid-upis::prefix is optional. Identifiers are compared case-insensitively.pay_onis the date you will order the payment. It defaults to today (UTC).- Batch:
{"preflights": [ {...}, {...} ], "known_payees": [...], "pay_on": "..."}, up to 200 per run. The top-levelknown_payeesandpay_onapply to every item that doesn't set its own.
Output
The structure below is exact; the values depend on your input. Keys of checks you did not switch on (for
example peppol without a peppol_id) may be missing. checks.iban_change summarises the K1/K2 known-payee
result; checks.payee_check is the full Payee Check rule list (I*, K* and T* rules), so walk that array to see
every IBAN, known-payee and invoice-text rule in one place.
{"verdict": "BLOCK","score": 0.2,"checks": {"iban": {"ok": true, "iban": "NL91ABNA0417164300", "country": "NL", "bank": "ABN AMRO", "bic": "ABNANL2A"},"iban_change": {"flag": true, "detail": "Jansen Bouw B.V. was paid before on NL02RABO0123456789; this payment goes to NL91ABNA0417164300. Confirm by phone on a number you already had"},"vat": {"vat_id": "NL123456789B01", "country": "NL", "format_ok": true, "source": "VIES","status": "valid", "valid": true, "name": "JANSEN BOUW B.V.", "name_match": 1.0,"checked_at": "2026-10-26T18:51:19Z", "cached": false, "in_invoice_text": true},"peppol": {"registered": true, "status": "listed", "source": "Peppol Directory", "participant_id": "0106:12345678","country": "NL", "entity_name": "Jansen Bouw B.V.", "receives_invoices": true,"checked_at": "2026-10-26T18:51:19Z", "cached": false},"vat_math": {"ok": false, "rates_checked": "NL","computed": {"total_net": "100.00", "total_vat_line_rounding": "21.00","total_vat_total_rounding": "21.00", "total_gross": "121.00"},"reverse_charge_expected": true, "reverse_charge_text_found": false, "vat_charged": true,"issues": ["NL supplier to BE business buyer with VAT charged: usually 0 % with reverse charge (services) or as an intra-Community supply (goods); some supplies are exceptions","NL to BE B2B: no 'btw verlegd' / reverse-charge or intra-Community supply wording found in the invoice text"]},"settlement": {"pay_on": "2026-12-25", "pay_on_is_target_day": false, "target_closed_reason": "Christmas Day","sepa_ct_value_date": "2026-12-28", "sepa_ct_latest_credit_date": "2026-12-29","instant_available": true,"instant_basis": "payee bank is in the euro area: must accept SCT Inst under Regulation (EU) 2024/886","pay_on_in_past": false},"payee_check": [{"rule": "I1 IBAN format and length", "ok": true, "severity": "block", "detail": "..."}]},"unavailable": [],"reasons": ["K2 known payee asks for a new IBAN: Jansen Bouw B.V. was paid before on NL02RABO0123456789; ...","M3 cross-border B2B VAT: NL supplier to BE business buyer with VAT charged: ...","M4 reverse-charge wording: NL to BE B2B: no 'btw verlegd' / reverse-charge or intra-Community supply wording found in the invoice text"],"engine": "supplier-preflight 0.2.0","disclaimer": "Indicative checks, not tax or legal advice."}
How the verdict is decided
- BLOCK: at least one block-level check failed. These are: an IBAN with the wrong length or check digits; a known payee name asking for a new IBAN; an invoice that lists IBANs but not the one being paid; or a VAT id that VIES says is invalid. A VAT id that is malformed while VIES is down also counts.
- REVIEW: at least one other check failed, or a register did not answer. In that case
unavailablelists"VIES"and/or"Peppol Directory". A lookup that could not be done never producesALLOW. - ALLOW: every check that your input switched on passed.
scoreruns from 0 to 1 and summarises the result: 1.0 means nothing failed. Each block-level failure subtracts 0.5, each other failure 0.15 and each unavailable register 0.1. Act onverdict, not onscore.
| Rule | Severity | Check |
|---|---|---|
| V1 | block | VAT id valid in VIES (also: VIES unavailable and the VAT id has an invalid format or Dutch checksum) |
| V2 | review | VAT id could not be checked: VIES unavailable, or the prefix is not an EU VIES country (e.g. GB, CH, NO) |
| V3 | review | the name VIES returns does not match payee.name (only when VIES returns a name; some countries, e.g. DE, never do) |
| V4 | review | the VAT id does not appear in invoice.text |
| P1 / P2 | review | the peppol_id you gave is not listed in the Peppol Directory, or the Directory did not answer |
| P3 / P4 | review | the Peppol entity's country or name differs from the VAT id's country or the payee name |
| M1 | review | VAT arithmetic: line VAT, sum of the lines, total VAT (rounding per line or per rate on the totals both pass), gross = net + VAT |
| M2 | review | a rate other than 21 / 9 / 0 % on an invoice from a Dutch supplier |
| M3 / M4 | review | B2B supply between two different EU countries: VAT charged anyway, or no reverse-charge / intra-Community wording in the text |
| D1 | review | issue_date is in the future |
| I*, K*, T* | as in Payee Check | the IBAN, known-payee and invoice-text checks listed below |
The settlement block is information only and never changes the verdict. sepa_ct_value_date is the first TARGET
business day on or after pay_on. TARGET is closed on weekends, 1 January, Good Friday, Easter Monday, 1 May,
25 December and 26 December. sepa_ct_latest_credit_date is the next business day after that (the D+1 rule).
instant_available is true for payee banks in the euro area, where SEPA Instant reception is mandatory. It is
null elsewhere, because there it depends on the bank.
Payee check ($0.001): offline, stateless
{"iban": "NL91ABNA0417164300","payee_name": "Jansen Bouw B.V.","amount": "1234.56","message_text": "Invoice 2026-118, amount EUR 1,234.56, to be paid to NL91 ABNA 0417 1643 00.","known_payees": [{"iban": "NL91ABNA0417164300", "name": "Jansen Bouw BV"}]}
Only iban is required. Here amount must be a decimal string (unlike the pre-flight invoice amounts, which also
accept JSON numbers). For a batch, send {"payments": [ {...}, ... ], "known_payees": [...]} with up to 1000
payments; a payment that sets its own known_payees uses that list instead of the top-level one.
{"verdict": "ALLOW","iban": "NL91ABNA0417164300","country": "NL","bank": {"name": "ABN AMRO", "bic": "ABNANL2A"},"reasons": [],"checks": [{"rule": "I2 IBAN check digits (mod 97)", "ok": true, "severity": "block", "detail": "check digits valid"}],"note": "Offline heuristic check, not a bank Verification of Payee. BLOCK/REVIEW means: a human confirms with the payee on a known phone number before paying."}
Rules: I1/I2 IBAN length and mod-97 (block). I3/I4 a non-SEPA country, or a Dutch bank code that is not on the official Dutch list (review). K1 a known IBAN under a different name (review). K2 a known payee name with a new IBAN (block). K3 a new payee (review). K4 a payee name that mixes Latin with Cyrillic or Greek look-alike letters (review). T1 the IBAN being paid is not among the IBANs in the message (block). T2 to T6 (review): several IBANs in the message, pressure language, bank-change language (Dutch, English, German, French, Spanish, Italian), or the amount or payee name missing from the message. Names and message text are compared as a reader sees them: full-width characters, invisible zero-width characters and Cyrillic/Greek look-alike letters can't be used to dodge a rule.
Attested verdicts: proof that you checked before you paid
When a payment goes wrong, the question is often "did you check before you paid?". A bank, an auditor, an
insurer or a financial dispute body (for example the ombudsman for financial services in your EU country) may
ask for that proof. A log line in your own system is weak evidence, because you could have written it afterwards.
With "attest": true, each checked item gets an evidence record that cannot be back-dated or changed afterwards
without it showing, not by you and not by us. Two independent witnesses date it: a Time-Stamp Authority signs it
at once, and a Bitcoin anchor follows within hours that does not depend on trusting anyone:
{"iban": "NL91ABNA0417164300", "payee_name": "Jansen Bouw B.V.", "attest": true}
Each result item then has an attestation object:
| Field | What it is |
|---|---|
status | ok (timestamped, charged as attested-check) or unavailable (the timestamp service did not answer: you still get the verdict, and the attestation is not charged) |
document | Canonical JSON (sorted keys, no spaces, UTF-8) of what was checked: the input, the full result (verdict, reasons, every rule), engine (rule version), data_as_of, the Actor build and run id, the check time and a random salt |
sha256 | SHA-256 of document |
merkle | All items of one run share one timestamp. path leads from your item's sha256 to the root that was timestamped (R: sha256(hash + x), L: sha256(x + hash)). For a single item, root = sha256 |
rfc3161 | An RFC 3161 timestamp token from FreeTSA (freetsa.org, a free public Time-Stamp Authority) over merkle.root: gen_time, serial, and the full signed response as tsr_b64 |
opentimestamps | An OpenTimestamps proof (ots_b64, a standard .ots file for your item's sha256), submitted to four public calendars. It starts as pending and can be anchored in the Bitcoin blockchain a few hours later with ots upgrade |
The long text fields (message_text, invoice.text) and your known_payees list are recorded in document as
{"sha256", "length"} only. Keep the original e-mail or invoice: its SHA-256 proves which text was checked.
Verify an attestation, free. Run the Actor with {"verify": [<attestation>, ...]} (the attestation object or
the whole result item). It checks offline, without calling any service, that the document still matches its hash,
that the Merkle path reaches the timestamped root, and that the RFC 3161 token is signed by the FreeTSA key
(certificate chain pinned to the FreeTSA root, timeStamping key usage, nonce). It returns valid and the list of
problems. Verification is not charged. You can also check it with standard tools and without us:
echo '<rfc3161.tsr_b64>' | base64 -d > t.tsrcurl -sO https://freetsa.org/files/cacert.pemopenssl ts -verify -in t.tsr -digest <merkle.root> -CAfile cacert.pem # "Verification: OK"echo '<opentimestamps.ots_b64>' | base64 -d > p.otsots upgrade p.ots && ots verify -d <sha256> p.ots # after a few hours: Bitcoin block
What this proves: the exact document (and so this verdict, rule version and data dates for this input) existed
no later than gen_time, as signed by an independent TSA, and, once anchored, no later than the Bitcoin block
time. What it does not prove: it is not an eIDAS qualified electronic time stamp (FreeTSA is not a qualified
trust service), so courts weigh it like other electronic evidence rather than presuming its accuracy. It does not
prove that the payment was made, or what you did with the verdict.
Calling it from an agent (MCP)
Add the Apify MCP server with this Actor to your MCP client, then call the Actor's tool with the JSON input above:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com?tools=lerider/payee-check","headers": {"Authorization": "Bearer <your Apify token>"}}}}
You can also call it from the Apify API (run-sync-get-dataset-items) or from the Apify client libraries. Results
come back as dataset items, one per payment or pre-flight. Each item has an index, and bad items carry an error
instead of a verdict and are not charged, for example {"index": 0, "error": "payee.iban is required"}. Required
fields are checked by the Actor at run time; the input schema itself is deliberately permissive because one
Actor serves two input shapes. The payee check's note and the pre-flight's disclaimer carry the same kind of
text. engine is the version of the check logic and changes independently of the Actor build version.
Pricing and charging
payee-check: $0.001 per checked payment (about $1 per 1,000).supplier-preflight: $0.03 per pre-flight.attested-check: $0.02 per item, on top of the check, only with"attest": trueand only when the RFC 3161 timestamp was obtained. If the timestamp service is down, the verdict is returned with"attestation": {"status": "unavailable"}and only the check itself is charged.verifyruns are free.- A malformed item, such as a missing IBAN, a bad date, an amount with more than 2 decimals or a wrong field type,
returns an
errorand is not charged. A batch is charged per item checked, not per run. - A pre-flight whose register lookup was
unavailablestill returns a verdict and is charged. Its verdict is neverALLOW, andunavailablenames the source, so you can re-run it later.
What it does NOT do
- It does not pay anything or move money. It is a read-only check.
- It is not your bank's Verification of Payee (VoP). It runs earlier, on the invoice text and your own payment history, which your bank's VoP never sees. VoP only warns, and a warning can be clicked through: the Dutch financial ombudsman Kifid holds that whoever pays despite a name-mismatch warning carries the loss (Kifid 2026-0901: €1,420 paid to a tampered address-book payee after the bank showed "name does not match", claim rejected). An agent paying on its own cannot weigh a warning; it needs the verdict before it pays.
- It gives no tax or legal advice. The VAT checks are arithmetic and pattern checks that flag what a person should look at. Reverse-charge rules have exceptions (for example some property, event and passenger-transport services), and only you or your adviser can apply them.
- It checks VAT rates only for Dutch suppliers. Other countries' rates are reported as
"rates_checked": null, never guessed.
Data sources
-
VIES (European Commission VAT Information Exchange System, public REST service). It is retried with backoff, and answers are cached in memory for at most 24 h, keyed on the VAT id.
-
Peppol Directory (OpenPeppol, public search API). The Directory lists participants that chose to publish their details, so "not listed" does not prove a company is not on the Peppol network.
-
TARGET calendar (European Central Bank), Dutch VAT rates (Belastingdienst), Dutch bank codes (Betaalvereniging Nederland BIC list) and the euro-area country list (European Union). These are built in. An automated check compares them with the official sources every month and flags any difference for an update. Every result carries
data_as_of, the date each built-in table was last verified, e.g.{"bic_list_nl": "2026-10-27", "vat_rates_nl": "2026-10-26", "euro_area": "2026-10-26"}. -
FreeTSA (freetsa.org), RFC 3161 Time-Stamp Authority, used for
attestonly. Its usage policy allows commercial use, at most one request per second per IP, best effort without an SLA. One request per run, whatever the batch size. -
OpenTimestamps public calendars (opentimestamps.org, eternitywall.com, catallaxy.com), used for
attestonly.
Privacy
Nothing is stored. Each run keeps lookup answers in memory only for its own lifetime, so a batch doesn't ask VIES twice for the same VAT id. Nothing is written to disk or logged. For sole traders, the trader name that VIES returns can be personal data. It is passed through to your result only and kept nowhere else. Apify keeps your run's dataset for your own account, under Apify's own retention rules. Send only what the checks need.
With attest, only one 32-byte SHA-256 per run (the Merkle root) goes to FreeTSA, and a hash of it with a random
nonce goes to each OpenTimestamps calendar. Every record contains a random salt, so the hash cannot be used to guess
an IBAN or a name. The evidence record itself is returned only to you, in your dataset.
Limits
- VIES and member-state registers go down at times. When that happens the verdict is
REVIEWwith"unavailable": ["VIES"]. Re-run it later. - Dutch bank-code recognition covers the main Dutch retail and business banks only.
- A batch of pre-flights makes live lookups, so it takes longer than a
payee-checkbatch: allow about 1 s per new VAT or Peppol id, and more when VIES retries. attestdepends on FreeTSA, a free service without an SLA. When it does not answer within about 25 s (one retry), the item is returned unattested and the attestation is not charged.- These are heuristic checks, not a guarantee. On
REVIEWorBLOCK, a person should confirm with the payee on a phone number they already had, not one taken from the invoice in question.
Published by Lerider Technologies.