Use case · Healthcare

Ingest claim line items with CPT and ICD-10 checked first.

Billing files arrive per clearinghouse, per payer, per month. Map them to claims_line_items_v1, have every CPT and ICD-10 code checked before the batch is written, and emit FHIR Claim.item.

template_id: claims_line_items_v1
CLAIMS_202602_PAYER_A.csv

What you actually get sent

  • Proc Code
  • Dx1
  • Billed Amt
  • DOS
  • Rendering NPI
  • Claim #

Abbreviated headers, an amount with a currency symbol in it, a service date as 02/14/2026, and an NPI column the target template does not declare.

The job

What you are actually trying to get done

Every payer and every clearinghouse hands you a different line-item layout, and the fields that matter are codes: a wrong CPT is a denial, a wrong ICD-10 is a resubmission. The job is to get each file into one shape, prove the codes are well-formed before you write anything, and keep an auditable record of which mapping produced which batch.

Where it lands

Claims line items

claims_line_items_v1 · risk: medium · FHIR Claim.item

The template declares fhir_resource: "Claim.item" — the line-item half of the two-level Claim split in lib/fhir.ts. Columns the template does not declare (a rendering NPI, a claim number) are not lost: carry them through POST /v1/reshape, or map them to a schema of your own with POST /v1/schemas.

The full field table →

Canonical columns

  • cpt_code
  • icd10_code
  • amount
  • service_date

4 canonical columns · 2 required · 2 validated

Step by step

The whole run, one call at a time

  1. 1

    Upload the batch

    CSV, TSV, Excel, JSON, XML or a SQL dump — all parsed in-process, nothing egresses to read them. 10 MiB ceiling by default (MAPR_MAX_UPLOAD_BYTES).
    POST /v1/uploads
  2. 2

    Map the abbreviations

    Proc Code reaches cpt_code through the cpt and procedure hints; Dx1 reaches icd10_code on the fuzzy layer against icd / icd-10. Neither needed a model.
    POST /v1/uploads/:id/match
  3. 3

    Check the codes per row

    cpt_code and icd10_code validators run over every row. Errors come back with their row index, capped at 500 per response so a bad batch cannot return a 40 MB error list.
    POST /v1/uploads/:id/validate
  4. 4

    Commit and record the layout

    The response carries accepted, skipped and a mapping_fingerprint. Deliver inline (≤10,000 rows), in HMAC-signed webhook batches of 500, or straight into a database through a saved destination connector.
    POST /v1/uploads/:id/commit
  5. 5

    Reuse it next month

    Same payer, same header row, same fingerprint — the confirmed mapping comes straight back and the cascade does not run at all. The flat per-map fee still applies; the AI cost does not, because no AI ran.
    POST /v1/layouts/lookup

In code

The mappings call, and the flag it returns

Confirming a mapping is also where the review flag appears. It is derived from the template’s own risk field, so there is no per-id list to maintain and no setting to forget.

curl
curl -X PATCH https://api.adaptivmapr.com/v1/uploads/upl_8f3a2c/mappings \
  -H "Authorization: Bearer $ADAPTIVMAPR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mappings": [
      { "source_col": "Proc Code", "target_field": "cpt_code" },
      { "source_col": "Dx1",       "target_field": "icd10_code" }
    ]
  }'
response
{
  "upload_id": "upl_8f3a2c",
  "mappings": [
    { "source_col": "Proc Code", "target_field": "cpt_code",   "source": "user" },
    { "source_col": "Dx1",       "target_field": "icd10_code", "source": "user" }
  ],
  "requires_hitl": true,
  "hitl_status": "pending_review"
}
→ requires_hitl derived from claims_line_items_v1 risk: "medium" · advisory in v1, not a server-side block
  • A user override is recorded and feeds mapping_statistics, the cascade’s layer 1. Twenty unanimous confirmations of the same header→field pair and it auto-accepts there next time — before any string is compared.
  • A target field, once assigned, cannot be reused by another header in the same call. Two source columns can never map to one target, and reversing your column order cannot change the result.
  • Idempotency-Key is NOT read on inbound requests. Retrying a failed commit produces a second map and a second flat fee — build your retry logic around the mapping_fingerprint, not an assumed guarantee.
  • Webhook batches are signed with X-Mapr-Signature and delivered through a Cloudflare Queue with a dead-letter queue and admin replay. GET /v1/uploads/:id/delivery reports per-batch status.

Where the work lands

Which layer resolves this file

LayerWhat it does hereAuto-accepts atCost
1 · statisticsThis payer’s exact header row, after ~20 unanimous confirmations≥100 @ 95% · ≥20 @ 100%Free · deterministic
2 · heuristic“Proc Code” → cpt_code and “Dx1” → icd10_code via the shipped cpt / icd hints≥ 0.85Free · deterministic
3 · fuzzy“Billed Amt” → amount and “DOS” → service_date after normalization≥ 0.80Free · pure compute
4 · semanticA payer-specific label that shares no vocabulary with the field≥ 0.78Cheap, cached · off without an embedding key
5 · aiOne batched call over the leftovers, told which fields are already claimedModel pick, constrained to the unclaimed column setMetered · the only paid layer

Layers run in strict cost order and short-circuit — the first layer that resolves a column claims the target field and no later, dearer layer sees it. Layers 4 and 5 are config-gated (MAPR_EMBEDDING_API_KEY, MAPR_LLM_API_KEY) and fail soft to OFF, so an unconfigured deployment still maps on the deterministic layers rather than erroring.

Before you commit

Review, cost and where it routes

It comes back flagged for review

claims_line_items_v1 is a medium-risk template, so PATCH /v1/uploads/:id/mappings returns requires_hitl: true and hitl_status: "pending_review" — automatically, from the template’s own risk field.

Honest limit · The flag is advisory in v1: it tells your importer to gate, it does not block the commit server-side. Native AgentGate queue integration is roadmap Q3 2026.

Usually just the flat fee

When every column resolves on layers 1–3 — the common case for a file you receive regularly — no model runs, so there are no AI tokens to bill. A small flat per-map fee ($0.0010) is drawn on every map — a fully deterministic one and a layout-cache re-map included. There is no free tier and no subscription; you top up a prepaid wallet from $10 and it draws down.

Rate card · AI tokens are billed only when a model actually ran, at provider cost ×2 (×0.5 with your own LLM key). A PHI-routed run multiplies the whole charge — flat fee included — by 1.2. See lib/pricing.ts and GET /v1/pricing.

PHI routing is opt-in, and gated

Standard is what an unconfigured workspace gets. Switch a run to PHI · enterprise and the layer-5 call goes to phi-cloud with X-PHI and a region pin, so a PHI-eligible in-region model handles it under the BAA.

Gate · Locked until the workspace accepts the BAA in-app (Settings → Security & Data, POST /v1/me/agreements). An explicit PHI ask without one is 403 agreement_required with a settings_url pointer — never a silent downgrade. PHI adds 20% to the whole map charge.

Questions

The things people actually ask

No. cpt_code is a well-formedness check, not a lookup against a licensed code set. It catches free text, wrong lengths and obviously malformed values. Existence and coverage checking against a specific release belongs in your adjudication logic, where the licence for that code set lives.
On the flat mapping path, an unmapped source column is reported in unmapped and does not appear in the committed rows — nothing is silently invented. If you need it kept, either define your own schema (POST /v1/schemas) that declares it, or use POST /v1/reshape, which supports pass-through columns into the produced sheet. The npi validator exists and is used by provider_directory_v1.
Inline delivery is capped at 10,000 rows (inline_too_large above that). Webhook delivery batches at 500 rows per signed request with no such ceiling, and a database destination writes in batches of 500. Batches are independent — there is no cross-batch transaction — so a non-empty failed_batches means a partial write, and a 200 is not proof of full delivery.
Yes, through a saved destination connector on the commit call — database: { connector_id, table }. sql_write posts INSERT text to a SQL-over-HTTP endpoint; supabase writes JSON rows to PostgREST and is the only destination that supports upsert. Warehouse (BigQuery, Snowflake, Databricks) and SaaS destinations are wired too. Credentials are KEK-encrypted.

Ready when you are

Stop hand-mapping this file. Map it once.

Start with a $10 prepaid wallet. In schema-only mode only headers and up to three clamped sample rows ever leave you.

$10 minimum to start · pay only for what you map · PHI under BAA coverage