Bank / account master list
CSV or XLSX, one row per account
IBAN written with spaces, without spaces, or lower-cased; the holder column named Kontoinhaber, titulaire, titolare or just Name; BIC sometimes present, sometimes a bank name instead.
Solution · Finance & payments
A transposed digit in an IBAN does not fail at import. It fails at the bank, on a payment run, with your name on it.
Finance & Payments
The job
What changes
What actually arrives
Banks, PSPs, ERPs and spreadsheets all export the same objects in different shapes, and a transposed digit in an IBAN surfaces on a payment run, not at import. The finance pack ships four templates with checksum-strict banking validators, and the three that touch money or identity gate on review before they commit.
Four shapes cover most finance onboarding. Each is a correct export from the system that produced it, and each is a different mapping problem.
CSV or XLSX, one row per account
IBAN written with spaces, without spaces, or lower-cased; the holder column named Kontoinhaber, titulaire, titolare or just Name; BIC sometimes present, sometimes a bank name instead.
CSV, one row per transaction
Card brand as a code, a name or an icon label; amounts in minor units in one report and decimal in the next; statuses that differ per processor — settled, captured, paid_out — for the same event.
XLSX, often with a title row above the headers
Comma decimal separators, a due date in DD.MM.YYYY, tax id columns labelled UST-ID, Partita IVA or NIF, and an invoice number that is numeric in one export and prefixed in another.
CSV or JSON, one row per subject
National-id formats that vary by country, dates of birth in two formats in the same file, and a risk rating expressed as a word, a letter or a number depending on the vendor.
Finance & Payments — every field carries DE / FR / IT / EN / ES hints.
1 medium + 3 high — PATCH /mappings returns requires_hitl: true.
iban, bic, date_range, regex
How it works
Upload, cascade, confirm, commit. The deterministic layers do most of the work before anything metered runs, and nothing but headers and a few clamped sample rows leaves your system on the way.
An invoice ledger, a payout file, a bank-account list or a KYC extract. In schema-only mode only the headers and up to three clamped sample rows leave your system, so the everyday mapping case moves no account data at all.
invoices_v1, payments_v1, bank_accounts_v1 or kyc_profiles_v1. Multilingual hints do the heavy lifting — Kontoinhaber, titulaire, titolare and titular all resolve to account_holder on the free heuristic layer, so most files never reach a metered model.
IBAN runs the ISO 13616 mod-97 checksum plus country and length checks; BIC is format-checked. Failures come back per row with a specific code — iban_checksum, iban_country, iban_length, iban_format — so a bad row is actionable, not just rejected.
payments_v1, bank_accounts_v1 and kyc_profiles_v1 are high-risk and invoices_v1 is medium, so all four come back with requires_hitl: true. Your workflow decides how to gate; commit then delivers inline, to a signed webhook, or into a saved database connector.
Field level
The template that carries the strictest arithmetic on the site. Every row below is a field object the catalogue ships, with the validator ids that run on it and the header vocabulary the free heuristic layer already recognises.
bank_accounts_v1high6 fields · 2 required · 2 validated| Field | Type | Required | Validators | Header vocabulary it already knows |
|---|---|---|---|---|
account_holder | string | required | — | kontoinhaber · titulaire · titolare · account holder +1 |
iban | string | required | iban | iban · compte · konto · bank account |
bic | string | — | bic | bic · swift · swift code |
account_number | string | — | — | kontonummer · numéro de compte · numero di conto · account number +1 |
routing_number | string | — | — | bankleitzahl · blz · code guichet · aba +1 |
currency | string | — | — | währung · devise · valuta · moneda |
GET /v1/templates/bank_accounts_v1 — not a description of one. The hint lists are what the heuristic layer compares a header against after normalize() strips accents, punctuation and whitespace, which is why adding a hint is the highest-leverage change in the whole engine: it costs nothing to run and removes a model call.Why AdaptivMapr
What the finance & payments pack gives you that a generic importer does not — and, under each card, the mechanism that does it.
IBAN is not pattern-matched, it is checksummed: country prefix moved to the end, letters expanded to digits, mod-97 evaluated in chunks. A transposed digit or a wrong country prefix fails at map time.
HowvalidIban() in lib/validators.ts returns iban_format, iban_country, iban_length or iban_checksum. Per-template country allowlists (CH, LI, DE, FR, IT, ES) reject an identifier that is well-formed but out of scope.
Risk is a property of the template, not a setting you remember to turn on — so every mapping against payments, bank accounts or KYC comes back flagged, on every call, for every workspace.
HowPATCH /v1/uploads/:id/mappings returns requires_hitl: true and hitl_status: "pending_review" whenever risk is medium or high. Three high + one medium here. AdaptivMapr sets the flag; your workflow owns the queue.
payments_v1 has no PAN field. It maps card_last4 and card_brand only — masked data by construction — so a full card number cannot be imported through this template even if a source file contains one.
Howcard_last4 is regex-constrained to ^\d{4}$. Combined with schema-only, where only headers and ≤3 clamped rows are sent, the everyday mapping path moves no cardholder data.
A run that needs a model gets one in the region your workspace pinned — on standard routing as well as PHI. Where compute may run is decided separately from which catalogue of models is eligible.
HowThe sandbox refuses a region-less run. Standard keeps the workspace region pin and picks the general catalogue; PHI adds X-PHI so phi-cloud forces a residency-locked, BAA-eligible model.
The frame
Each frame below is paired with a mechanism, not an adjective. Where we hold nothing, the row says so — a compliance page that overstates is worse than one that is short.
Any system that stores, processes or transmits a primary account number falls inside PCI scope, with everything that follows from it.
Art. 5(1)(c): process the minimum personal data necessary. A KYC or bank file handed wholesale to a model is more than a mapping needs.
A well-formed-looking IBAN is not a valid one; the check digits exist precisely because transpositions are common and expensive.
A finance team has to be able to say who confirmed a mapping, when, and what it produced.
What we do not claim: AdaptivMapr is not a PCI-certified service provider and does not need to be for this template set, because the templates carry no cardholder data. SOC 2 is in progress. And this is the mapping layer, not a payments processor, a reconciliation product or a hosted onboarding UI in place of Flatfile.
Risk & review
Every template this vertical ships, with the risk level that decides whether a commit gates on human review.
| Template id | Risk | Commit gate | Validators |
|---|---|---|---|
payments_v1 | high | requires_hitl: true | regex |
bank_accounts_v1 | high | requires_hitl: true | iban, bic |
invoices_v1 | medium | requires_hitl: true | — |
kyc_profiles_v1 | high | requires_hitl: true | date_range, regex |
GET /v1/templates returns. Nothing on this table is typed by hand.There is no setting for it and no per-id list to maintain. A template rated medium or high makes PATCH /mappings return requires_hitl: true and hitl_status: "pending_review" — on every call, for every workspace, derived in one line of the route from the risk field. AdaptivMapr sets the flag; your workflow owns the queue. A native review queue is on the roadmap, not in v1.
In this vertical, out of 4 templates.
Across all 7 packs the engine serves on GET /v1/templates.
[
{ "source_col": "Kontoinhaber",
"target_field": "account_holder",
"user_confirmed": true }
]{
"upload_id": "upl_7a41c2…",
"mappings": [ … ],
"requires_hitl": true,
"hitl_status": "pending_review"
}In code
A bank-account list against bank_accounts_v1 — four German headers, four hits on the free heuristic layer, then a row-level validate pass that runs mod-97 over every IBAN before anything is committed.
/v1/uploadsparse the file, return its headers + ≤3 clamped sample rows/v1/uploads/:id/matchrun the six-layer cascade against one template/v1/uploads/:id/mappingsrecord confirmations; returns requires_hitl/v1/uploads/:id/validaterun the field validators over every row, indexed/v1/uploads/:id/commitemit and deliver — inline, webhook or DB connectorAuthentication is one header. Keys are HMAC-signed and carry their own tenant_id, so verifying one needs no database lookup — and a key can be scoped so the service that reads mappings is not the one allowed to commit them.
# 1 · upload (schema-only)
curl -s https://api.adaptivmapr.com/v1/uploads \
-H "Authorization: Bearer $MAPR_KEY" \
-F file=@bankkonten.csv
# 2 · map to the finance template
curl -s https://api.adaptivmapr.com/v1/uploads/$ID/match \
-H "Authorization: Bearer $MAPR_KEY" \
-H "Content-Type: application/json" \
-d '{"target_schema":{"schema_id":"bank_accounts_v1"},
"mode":"schema-only"}'
# 3 · run the row validators (mod-97 over every IBAN)
curl -s https://api.adaptivmapr.com/v1/uploads/$ID/validate \
-X POST -H "Authorization: Bearer $MAPR_KEY"{
"template_id": "bank_accounts_v1",
"row_count": 1284,
"error_count": 2,
"errors": [
{ "row_index": 391, "field": "iban", "code": "iban_checksum",
"message": "IBAN checksum failed" },
{ "row_index": 902, "field": "iban", "code": "iban_country",
"message": "country GB not allowed" }
],
"warnings": []
}Rollout
Four phases, none of which requires the previous one to be finished. Most teams stop after the third and only wire the fourth when a customer starts sending the same file every month.
Upload a real bank or payout export in schema-only mode, map it, then run validate. You get a row-indexed list of every IBAN that fails mod-97 in the file you have been importing by hand — before writing any integration code.
uploads → match → mappings → commit, with a scoped API key. Keys are HMAC-signed and carry their own tenant_id, so verification needs no lookup and PATCH can be restricted to the service that is allowed to confirm.
payments_v1, bank_accounts_v1 and kyc_profiles_v1 are high-risk and invoices_v1 is medium, so all four return requires_hitl. Route that flag into whatever already approves a payment file. We set the flag; your workflow owns the queue.
A confirmed layout is cached against a fingerprint of the normalized header row, so next month’s file of the same shape is a lookup. Connectors pull on a schedule with KEK-encrypted secrets; delivery is a signed, queued webhook with a dead-letter queue and admin replay.
FAQ
Start on schema-only — headers and a few clamped sample rows, a few tokens per map. Turn on PHI routing when you need it: in-region, under a BAA you accept in the app.