IBAN & EU VAT Validator: VIES, Peppol, Invoice Fraud Check avatar

IBAN & EU VAT Validator: VIES, Peppol, Invoice Fraud Check

Pricing

from $1.00 / 1,000 payee checks

Go to Apify Store
IBAN & EU VAT Validator: VIES, Peppol, Invoice Fraud Check

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

Lerider Technologies

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

14 hours ago

Last modified

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.

EventPriceWhat it does
payee-check$0.001Offline 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.03Everything 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.02Optional 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_rate is a percentage. Lines may also carry their own vat amount, which is then checked too.
  • peppol_id is ICD:identifier, for example 0106:12345678 (Dutch KvK), 0208:0123456789 (Belgian enterprise number) or 9944:NL123456789B01. The iso6523-actorid-upis:: prefix is optional. Identifiers are compared case-insensitively.
  • pay_on is 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-level known_payees and pay_on apply 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 unavailable lists "VIES" and/or "Peppol Directory". A lookup that could not be done never produces ALLOW.
  • ALLOW: every check that your input switched on passed.
  • score runs 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 on verdict, not on score.
RuleSeverityCheck
V1blockVAT id valid in VIES (also: VIES unavailable and the VAT id has an invalid format or Dutch checksum)
V2reviewVAT id could not be checked: VIES unavailable, or the prefix is not an EU VIES country (e.g. GB, CH, NO)
V3reviewthe name VIES returns does not match payee.name (only when VIES returns a name; some countries, e.g. DE, never do)
V4reviewthe VAT id does not appear in invoice.text
P1 / P2reviewthe peppol_id you gave is not listed in the Peppol Directory, or the Directory did not answer
P3 / P4reviewthe Peppol entity's country or name differs from the VAT id's country or the payee name
M1reviewVAT arithmetic: line VAT, sum of the lines, total VAT (rounding per line or per rate on the totals both pass), gross = net + VAT
M2reviewa rate other than 21 / 9 / 0 % on an invoice from a Dutch supplier
M3 / M4reviewB2B supply between two different EU countries: VAT charged anyway, or no reverse-charge / intra-Community wording in the text
D1reviewissue_date is in the future
I*, K*, T*as in Payee Checkthe 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:

FieldWhat it is
statusok (timestamped, charged as attested-check) or unavailable (the timestamp service did not answer: you still get the verdict, and the attestation is not charged)
documentCanonical 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
sha256SHA-256 of document
merkleAll 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
rfc3161An 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
opentimestampsAn 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.tsr
curl -sO https://freetsa.org/files/cacert.pem
openssl ts -verify -in t.tsr -digest <merkle.root> -CAfile cacert.pem # "Verification: OK"
echo '<opentimestamps.ots_b64>' | base64 -d > p.ots
ots 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": true and 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. verify runs 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 error and is not charged. A batch is charged per item checked, not per run.
  • A pre-flight whose register lookup was unavailable still returns a verdict and is charged. Its verdict is never ALLOW, and unavailable names 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 attest only. 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 attest only.

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 REVIEW with "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-check batch: allow about 1 s per new VAT or Peppol id, and more when VIES retries.
  • attest depends 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 REVIEW or BLOCK, 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.