IBANs, countries and dates: normalized or left alone
Countries to ISO codes, IBANs verified by checksum, phones to E.164, dates to ISO 8601. And the rule that makes it safe: anything that does not resolve comes back exactly as printed.
6 min readAn invoice says the supplier is in "Deutschland", the currency is "EUR", the bank account is "DE89 3704 0044 0532 0130 00" and the phone is "089 12345678". Your database wants DE, EUR, a validated IBAN and +498912345678.
Getting from one to the other is the boring middle of every document pipeline, and it is where a surprising amount of bad data is created, because the usual approaches are a model guessing at a code and a pile of per country special cases in your application.
What comes back normalized
There are two levels to this, and the difference matters.
By default, extraction emits the normalized form when the document makes it unambiguous, and the value as printed when it does not. That covers most fields without you doing anything.
**Declare a format and it stops being a judgement call.** The model reads what is printed, exactly as printed, and the mapping runs afterwards in code, against real reference data, with checksum verification where the format has one:
That is the version to use for anything that reaches a database. The full list is in Schemas and formats.
The rule that makes this safe
Resolve or leave it alone. Never fabricate.
If a value does not resolve, you get the original text back, unchanged. Not a best guess, not a nearest match, not an empty string.
That applies at every step:
- A country name in a language or spelling we do not recognize stays as the printed name.
- A currency word with no ISO mapping stays as the word.
- An IBAN that fails its mod-97 checksum comes back as the raw text. It is not silently "corrected" into a valid IBAN, which would be the single most dangerous transformation in this entire list.
- A phone number with no recoverable country context stays as printed.
- A date whose parts are genuinely ambiguous is not forced into a confident ISO string.
The reason this rule matters more than the normalization itself: a raw value that came through unchanged is visible to you. You can detect it, log it, route it for review. A value that was quietly guessed into a valid looking code is invisible, and it is in your database now.
Why checksums are the interesting part
IBAN and several tax identifier formats carry a check digit computed from the rest of the string. That means a transcription error is usually detectable, not by comparing against a list of real accounts, but arithmetically.
So when an IBAN comes back validated, that is a real statement: the characters are internally consistent, and the most common failure, a misread digit, would have broken it. When it fails, you know the extraction is suspect before the payment run, not after.
This is the difference between a field that is formatted and a field that is checked.
The date problem specifically
03/04/2026 is 3 April in most of Europe and 4 March in the United States. No amount of staring at the number resolves it, and a system that quietly picks one convention is wrong for everybody else roughly half the time.
The right resolution comes from the document, not from a default: the language it is written in, the address on it, the other dates on the page, the format of the printed month. Our extraction identifies which part is which from that context, and the runtime builds the ISO string mechanically from the parts. Month names in the common Latin script languages resolve too, so 14 février 2026 and 14. März 2026 come back as ISO dates rather than as strings your application has to parse.
What you stop writing
For most teams this is a few hundred lines that quietly disappear:
- A country name to ISO code map, plus the aliases and the language variants.
- Currency symbol handling, including the ones that collide.
- A phone normalizer with country context.
- An IBAN checksum implementation.
- Date parsing per locale, with the ambiguous cases handled somehow.
- The rounding helper everyone writes for money.
Not writing that code is worth something. Not owning it forever is worth more.
Putting it together
Then handle the values that came back raw, because those are the ones telling you something.
The quickstart is a few minutes, and what else comes back verified covers the rest.