AdaptivMapr

Solution · Finance & payments

The payment file, checked first.

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.

IBAN, actually checked
mod-97
Finance templates
4
The pack

Finance & Payments

templates
4templates
gate on review
4gate on review
field validators
4field validators
hint languages
5hint languages
payments_v1bank_accounts_v1invoices_v1kyc_profiles_v1

The job

The error you find out about from the bank.

Banks, PSPs, ERPs and somebody’s spreadsheet all export the same objects in different shapes. The mapping is tedious; the real cost is the row that maps fine and is still wrong — an IBAN whose checksum fails, a KYC record missing the field a regulator will ask about.

What changes

IBAN and BIC are verified by checksum before anything is committed, not matched against a shape. A row that fails says which field failed and why, and it is never silently written.

What actually arrives

Every source exports it differently.

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.

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.

PSP payout / settlement report

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.

ERP accounts-receivable ledger

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.

KYC vendor extract

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.

4 templates

Finance & Payments — every field carries DE / FR / IT / EN / ES hints.

4 gate on review

1 medium + 3 high — PATCH /mappings returns requires_hitl: true.

4 field validators

iban, bic, date_range, regex

  • Headers + ≤3 rows, 80 chars each
  • Layers 1–3 need no model
  • requires_hitl on medium + high
  • PHI pinned in-region under a BAA
  • Per-workspace, never cross-tenant

How it works

Four calls, and the file is in your schema.

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.

  1. 1Step 1

    Upload the finance export

    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.

    POST /v1/uploadsschema-only
  2. 2Step 2

    The cascade maps to a finance template

    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.

    invoices_v1payments_v1bank_accounts_v1kyc_profiles_v1
  3. 3Step 3

    Validate the banking identifiers

    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.

    ibanbiciban_checksum
  4. 4Step 4

    Review the high-risk ones, then commit

    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.

    requires_hitlwebhooksql_write

Field level

One template, printed in full.

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
FieldTypeRequiredValidatorsHeader vocabulary it already knows
account_holderstringrequired—kontoinhaber · titulaire · titolare · account holder +1
ibanstringrequiredibaniban · compte · konto · bank account
bicstring—bicbic · swift · swift code
account_numberstring——kontonummer · numéro de compte · numero di conto · account number +1
routing_numberstring——bankleitzahl · blz · code guichet · aba +1
currencystring——währung · devise · valuta · moneda
Every row is a field object from 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

Built for the shape of regulated data.

What the finance & payments pack gives you that a generic importer does not — and, under each card, the mechanism that does it.

The same arithmetic a bank runs

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.

The risky templates say so themselves

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.

Cardholder data stays out of scope

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.

Region is a pin, not a preference

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

What applies here, and what we actually do about it.

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.

PCI DSS — cardholder data

Any system that stores, processes or transmits a primary account number falls inside PCI scope, with everything that follows from it.

Our posture · payments_v1 has no PAN field. It maps card_last4 — regex-constrained to ^\d{4}$ — and card_brand, and nothing else. A full card number cannot be imported through this template even if the source file contains one, so cardholder data is out of scope by construction rather than by policy.

GDPR / nFADP — data minimisation

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.

Our posture · Schema-only sends headers plus up to three sample rows clamped to 80 characters — enforced at the HTTP edge by one chokepoint, clampForSchemaOnly() in lib/parser.ts. For the everyday mapping case no account data moves at all. A DPA is provided on request.

ISO 13616 — IBAN (a standard, not a regulation)

A well-formed-looking IBAN is not a valid one; the check digits exist precisely because transpositions are common and expensive.

Our posture · The iban validator runs the real mod-97 arithmetic — country prefix moved to the end, letters expanded to digits, remainder must be 1 — plus a per-template country allowlist and a per-country length check. Failures return iban_format, iban_country, iban_length or iban_checksum with the row index.

Segregation and audit

A finance team has to be able to say who confirmed a mapping, when, and what it produced.

Our posture · Confirmations and commits are written to the audit trail with the mapping fingerprint, scoped to the workspace. Learned statistics and cached layouts are workspace-scoped and never shared across tenants; the dashboard plane runs under row-level security.

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

The pack, printed.

Every template this vertical ships, with the risk level that decides whether a commit gates on human review.

Template idRiskCommit gateValidators
payments_v1highrequires_hitl: trueregex
bank_accounts_v1highrequires_hitl: trueiban, bic
invoices_v1mediumrequires_hitl: true—
kyc_profiles_v1highrequires_hitl: truedate_range, regex
Read from the live template catalogue — the same ids, risk levels and validator ids that GET /v1/templates returns. Nothing on this table is typed by hand.

The gate is a property of the template.

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.

1 medium + 3 high

In this vertical, out of 4 templates.

13 flagged of 33

Across all 7 packs the engine serves on GET /v1/templates.

PATCH /v1/uploads/:id/mappings
[
  { "source_col": "Kontoinhaber",
    "target_field": "account_holder",
    "user_confirmed": true }
]
response · payments_v1
{
  "upload_id": "upl_7a41c2…",
  "mappings": [ … ],
  "requires_hitl": true,
  "hitl_status": "pending_review"
}
the body is an ARRAY of { source_col, target_field, user_confirmed } · 409 no_template if /match has not run on this upload yet

In code

A real call, end to end.

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.

  • POST/v1/uploadsparse the file, return its headers + ≤3 clamped sample rows
  • POST/v1/uploads/:id/matchrun the six-layer cascade against one template
  • PATCH/v1/uploads/:id/mappingsrecord confirmations; returns requires_hitl
  • POST/v1/uploads/:id/validaterun the field validators over every row, indexed
  • POST/v1/uploads/:id/commitemit and deliver — inline, webhook or DB connector

Authentication 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.

curl
# 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"
response · /validate
{
  "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": []
}
schema-only · headers + ≤3 rows clamped to 80 chars left your system · medium/high templates gate before commit

Rollout

From one file to a feed that runs itself.

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.

  1. 1

    Checksum a file you already have

    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.

    POST /v1/uploadsPOST /v1/uploads/:id/validate
  2. 2

    Wire the four calls

    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.

    Bearer mp_live_…commit scope
  3. 3

    Gate the money templates

    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.

    requires_hitlhitl_status
  4. 4

    Automate the recurring feed

    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.

    /v1/layouts/lookupconnectorswebhook DLQ

FAQ

The questions that decide it.

Do account numbers or card numbers get sent anywhere?
In schema-only mode only column headers and up to 3 sample rows, each clamped to 80 characters, leave your system — so the everyday mapping case moves no account data. And payments_v1 has no PAN field at all: it maps card_last4 and card_brand, so a full card number cannot be imported through it. Full-data mapping is a separate, explicit opt-in.
How is an IBAN validated?
With the ISO 13616 mod-97 checksum — country prefix moved to the end, letters expanded to digits, remainder must be 1 — plus a country allowlist and a per-country length check. Failures return iban_format, iban_country, iban_length or iban_checksum with the row index. BIC is validated for format.
Which finance templates require review?
payments_v1, bank_accounts_v1 and kyc_profiles_v1 are high-risk; invoices_v1 is medium. All four therefore return requires_hitl: true and hitl_status: "pending_review" from PATCH /v1/uploads/:id/mappings. The flag is derived from the template risk level, so it cannot be forgotten — but the review queue itself is yours. A native queue is on the roadmap, not in v1.
Can I run recurring imports without uploading by hand?
Yes. Connectors register a scheduled ingest source with KEK-encrypted secrets and pull on a cadence. And once a mapping is confirmed, the whole layout is cached against a fingerprint of the normalized header row, so POST /v1/layouts/lookup returns it and the next file of that shape can skip mapping entirely. Layouts are workspace-scoped — never shared across tenants.
What does a map cost?
There is no free tier. AdaptivMapr runs on a prepaid token wallet with a ~$10 minimum, shared across the phi-cloud suite. Every map draws a small flat fee — a few tokens — including a fully deterministic map and a layout-cache hit that uses no AI. Metered AI work bills the tokens it consumes on top, and a PHI-routed run adds 20% to the whole charge.
Are you a PCI-certified service provider?
No — and for this template set that is the point. payments_v1 has no PAN field: it maps card_last4 (constrained to four digits) and card_brand, so cardholder data never enters the system through it. A vendor that never receives a primary account number is out of PCI scope by construction, which is a stronger claim than a certificate over a system that does receive one. SOC 2 is in progress; we can share our security posture on request.
Can we run this with no AI in the loop at all?
Yes. The semantic layer needs an embeddings key and the LLM layer needs a phi-cloud key; both are config-gated and fail soft to OFF when the key is absent. With neither configured only the deterministic layers run — statistics, heuristic and fuzzy — and anything that does not resolve comes back in unmapped for a person to assign rather than being guessed by a model.
What happens when a mapping is wrong?
Every match carries a confidence and the cascade layer it came from, so a weak pick is visible before commit. You override it in PATCH /v1/uploads/:id/mappings with user_confirmed: true; that teaches the statistics layer for your workspace, and when your choice differs from the suggestion the original is recorded as a negative signal. Contests between two columns for one field are settled by layer rank then confidence — never by column order — and one target field can only ever be claimed once per run.
Is this a replacement for our importer or our ERP integration?
No. AdaptivMapr is the mapping engine and its API — a six-layer cascade, a template catalogue, checksum validators, layout reuse and delivery. It is not a drop-in replacement for a hosted onboarding UI such as Flatfile, and it does not move ledgers between systems. Teams run it as the mapping layer underneath their own import experience.

Map finance & payments data without shipping raw records.

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.

Finance and payments data mapping — AdaptivMapr — AdaptivMapr