AdaptivMapr

Capability · Validators

Catch the bad row before it ships.

A wrong IBAN is not a formatting problem, it is a failed payment. Nineteen checks run before anything is written, and a bad row says exactly what is wrong with it.

Field validators
19
Not regex shape
Checksum
Field-level validators checking IBAN, LOINC, ICD-10, NPI and GTINibanloincicd10npigtin

The job

The bad row is cheap now and expensive later.

A transposed digit in an IBAN passes a regex and fails at the bank. A National Provider Identifier that looks right fails its checksum three weeks into a claims cycle. A GTIN with a wrong check digit gets a product listed against the wrong item. Shape-checking catches none of these, because all of them are the right shape.

What changes

The checksums actually run — mod-97, Luhn, GS1 — alongside the code systems, before the row is committed. A row that fails is flagged with a precise code and the field that failed, and it is never silently written.

What you get

Not a demo. The thing that runs every day.

Strict

Real checksums, not regex theatre

A value that looks right but fails its check digit is caught. That is the whole difference between validation and a pattern that makes you feel better.

Howiban is verified mod-97 over the rearranged, letter-expanded string; gtin and gln against the GS1 check digit; npi by Luhn over "80840" + the first 9 digits. All pure compute, no network.

Country-aware

IBAN length by country, on request

The default check is the mod-97 checksum and a shape rule. Turn on strict mode and the length has to match the country too.

How{ type: "iban", strict: true } enforces the per-country length from the SWIFT IBAN Registry (Release 95) — 21 for CH, 22 for DE, 27 for FR. countries narrows the allowed set further.

Clinical

Healthcare code systems

LOINC, ICD-10, ATC and CPT are checked against their format rules, so a lab result or a claim line carries a code that will resolve downstream instead of failing in someone else’s system.

Howloinc_code is NNNNN-N, icd10_code is A00 or A00.0, atc_code is the ATC level pattern, cpt_code is five digits. Format checks, stated as format checks — none of these carry a published check digit we could verify.

Safety

A URL field will not fetch your metadata service

The url validator is not just a shape check. It refuses the addresses a fetched URL should never point at.

Howurl runs checkUrlForSsrfSync() and fails with ssrf_blocked for private ranges and link-local addresses; require_https tightens it to TLS only. A validator on a field that later gets fetched is a security boundary.

One chokepoint

Every path validates identically

The one-shot cascade, the upload commit pipeline and the reshape engine all run the same pass over the same rules. There is no path that writes rows unchecked.

HowvalidateRowsDetailed() in lib/rowValidation.ts is the single implementation. Reshape output used to bypass it; it does not any more.

Fail loud

Flagged, never silently committed

An invalid row comes back with the field, the validator code and the message. And on a risky template, a commit needs a human before it happens at all.

HowMedium- and high-risk templates return requires_hitl: true and hitl_status: "pending_review"; with MAPR_HITL_ENFORCE=1 (production) a commit without hitl_approved: true is refused with 428.

Reference

The nineteen validators

A mapping that finds the right column but writes a malformed value is still a bad import. Every field can carry validators, and they run on the resolved value about to be written — not as a loose regex, but as the real check where a real check exists. A row that fails is flagged or skipped with a specific code, so you always know which field broke and why.

  1. 01

    Declared on the field

    A template field — or your own schema’s field — carries its validators. The parameterised ones take your pattern, your allowed set, your bounds, your permitted countries.

  2. 02

    Run on the resolved value

    The cascade decides which source column feeds the field first. Validation then happens on the value about to be written, not on the raw cell in the file.

  3. 03

    Real algorithm where one exists

    Mod-97 for IBAN, the GS1 check digit for GTIN and GLN, Luhn for NPI. Where a code system publishes no check digit, the format is checked and named as a format check.

  4. 04

    Flagged, or gated

    A failure comes back as { col, code, message }. On a medium- or high-risk template the commit itself is gated on a human, not just annotated.

ValidatorWhat it checksExample
regexCustom pattern match^756\.\d{4}\.\d{4}\.\d{2}$
enumValue inside an allowed setpaid | unpaid | overdue
date_rangeDate within bounds1900-01-01 … today
number_rangeNumber within bounds0 … 120
emailWell-formed addressada@example.org
phonePhone shape, optional country set+41 44 000 00 00
urlWell-formed URL + SSRF refusalhttps://example.org
ibanMod-97 checksum (+ per-country length in strict mode)CH93 0076 2011 6238 5295 7
bicSWIFT / BIC structureUBSWCHZH80A
uuidRFC 4122 UUID9f1c8e2a-…-4c1d-…
gtinGS1 check digit (8/12/13/14 digits)4006381333931
pharmacodeSwiss pharmacode shape, 4–7 digits1234567
fhir_referenceResourceType/id reference formPatient/123
loinc_codeLOINC format4548-4
icd10_codeICD-10 formatE11.9
atc_codeATC formatA10BA02
cpt_codeCPT format99213
npiNPI Luhn check1234567893
glnGLN check digit (13 digits)7601234567890
SNOMED was deliberately removed in June 2026 — it requires a SNOMED International Affiliate License and no template used it, so carrying a snomed_code validator was a compliance liability with no benefit. There is no snomed_code validator and there will not be one.

Try it

One row in, every failure out

Check a single row against a template before you build the rest of your import. Public, unauthenticated, nothing persisted — 200 requests an hour per IP.

curl
curl https://api.adaptivmapr.com/v1/validate-row \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "bank_accounts_v1",
    "row": {
      "account_holder": "Ada Lovelace",
      "iban": "CH93 0076 2011 6238 5295 8",
      "bic":  "UBSWCHZH80A"
    }
  }'
response
{
  "template_id": "bank_accounts_v1",
  "ok": false,
  "errors": [
    { "col": "iban", "code": "iban_checksum", "message": "IBAN checksum failed" }
  ]
}
→ last digit changed 7 → 8 · caught by mod-97, not by a pattern
  • POST /v1/validate-row is public and unauthenticated — 200 requests an hour per IP, one row in, { ok, errors[] } out. Nothing is stored.
  • In a real import the same rules run at COMMIT, on the resolved value about to be written, through validateRowsDetailed() — so a bad row is flagged or skipped before it reaches your system of record.
  • A resolver-backed validator may substitute the canonical code it resolved, so the value that gets committed is the normalized one rather than the string the source happened to use.

The surface

Every route this page actually has.

One public route to check a single row while you build, and the commit-time surfaces where the same rules actually gate a write.

  • POST/v1/validate-rowOne row against one template, { ok, errors[] } out. Nothing persisted. 200 requests an hour per IP.no key
  • POST/v1/uploads/{id}/validateDry-run the confirmed mapping over a whole stored upload before you commit any of it.read scope
  • PATCH/v1/uploads/{id}/mappingsConfirm the mapping. Returns requires_hitl + hitl_status on any medium- or high-risk template — 13 of the ones the engine ships.commit scope
  • POST/v1/uploads/{id}/commitWhere the validators actually bite: rows that fail are flagged or skipped, and never written.commit scope
  • GET/v1/templates/{id}Every field of a template with its label, its multilingual hints and the validators declared on it.no key

Limits & failure modes

What it refuses to do — and the code it says it with.

A validator that cannot fail is decoration. These are the refusals — the codes you branch on, and the two places where we deliberately do less than you might expect.

iban_checksum

The IBAN is well-shaped but its mod-97 remainder is not 1.

A single transposed digit is caught here and nowhere else. { type: "iban", strict: true } additionally enforces the per-country length.

ssrf_blocked

A url field points at a private range, a link-local address or the metadata service.

A validator on a field that later gets fetched is a security boundary. require_https tightens it to TLS only.

428 hitl_required

A commit on a medium- or high-risk template without hitl_approved: true, with MAPR_HITL_ENFORCE=1.

Production runs with enforcement on. Review the mappings, then re-commit with the flag — the refusal names the template and its risk level.

required_missing

A field marked required resolved to an empty value.

Reported per column rather than as one opaque row failure, so a partially bad import tells you exactly which field to fix.

Format, not checksum

LOINC, ICD-10, ATC, CPT and pharmacode.

None of these publishes a check digit we could verify, so we validate the format and say so. Pharmacode in particular is a 4–7 digit shape check, not a checksum.

No snomed_code

You go looking for a SNOMED validator.

Removed in June 2026. SNOMED CT requires an Affiliate License and no template used it, so carrying it was a compliance liability with no benefit. It is not coming back.

What it costs

One prepaid wallet, drawn down per call.

No free tier. Top up from $10 — the balance is shared across the phi-cloud suite — and every operation draws it down at the rate below. An optional $6/mo plan grants a credit that resets each billing cycle instead; overage falls back to the wallet.

Every validator

Free

Pure compute, no network, no model — including the checksums. They add no token cost to any import they run inside.

POST /v1/validate-row

No key, no charge

Public and unauthenticated at 200 requests an hour per IP. Nothing is stored, so it is safe to wire into your own test suite.

A validated commit

$0.001

The flat per-map fee for the map itself. Validation is part of the map, not a line item on top of it.

A PHI/enterprise-routed run multiplies the whole charge by 1.2 (+20%), flat fee included — and only when the run genuinely got that routing. PHI stays locked until the workspace accepts the BAA in Settings → Security & Data. Full pricing

Questions

The ones asked before signing.

What it costs, what leaves your network, and the claims we will not make.

When do validators run?
At commit, on the resolved value about to be written — not as an afterthought on the way out. The cascade one-shot, the upload commit pipeline and the reshape engine all share one implementation, so there is no path that writes rows unchecked.
Are these just regexes?
Where a real check algorithm exists, we run it: IBANs mod-97, GTINs and GLNs against the GS1 check digit, NPIs by Luhn. Where none exists — LOINC, ICD-10, ATC, CPT, pharmacode — we validate the format and say so, rather than dressing a pattern up as a checksum.
Why is there no SNOMED validator?
It was removed on purpose in June 2026. SNOMED CT requires a SNOMED International Affiliate License and no template ever used it, so carrying a snomed_code validator was a compliance liability with no benefit. We do not plan to reintroduce it.
Can I add my own rules to a field?
Yes. regex, enum, date_range, number_range, phone, url and iban are parameterised, so a field can carry your pattern, your allowed set, your bounds, your permitted IBAN countries or an https-only requirement alongside the built-in checksum validators.
What happens on a high-risk template?
Medium- and high-risk templates come back with requires_hitl: true and hitl_status "pending_review" so your workflow can gate the commit. In production, enforcement is on: a commit on one of those templates without hitl_approved: true is refused with 428.

Keep reading

The rest of the same engine.

One key, from a messy file to your schema.

Top up a $10 prepaid wallet and start mapping. In schema-only mode only your headers and three clamped rows ever leave you.

Field validators — AdaptivMapr — AdaptivMapr