Platform
One engine. Any data in, your schema out.
A stateless, pure-compute mapping cascade that turns any CSV, Excel, JSON, or SQL into your schema — resolving most columns on free deterministic layers before any AI is touched.
- Headers + ≤3 rows, ≤80 chars a cell
- HMAC self-contained API keys
- RLS on the dashboard plane
- Uploads expire from KV — 24 h by default
- In-region PHI routing under a BAA
The cascade
Six layers, cost-ordered — the engine stops the moment a column resolves.
Each header runs down a ladder from free and deterministic to metered AI. The instant a layer is confident, the cascade accepts and moves on — so most columns are settled before any model is consulted. That early exit is the single biggest cost lever in the system.
- Layer 1
Statistics
Auto-accept from confirmed history: a header maps when past confirmations for it agree at a production-tested ratio.
{minN:100, minRatio:0.95} · {minN:20, minRatio:1.00}free · deterministic
- Layer 2
Heuristic
normalize() strips accents, punctuation and whitespace, then compares the header against each field’s column, label and every hint (DE/FR/IT/EN/ES).
exact = 1.00 · substring = 0.85free · pure compute
- Layer 3
Fuzzy
Token-set ratio plus Levenshtein over the normalized strings — tolerates typos and token-order drift ("Frist Name", "name first").
FUZZY_AUTO_ACCEPT = 0.80free · pure compute
- Layer 3½
Mapping ranker
A fine-tuned mDeBERTa cross-encoder we host ourselves scores every still-unresolved column against each template field and against "none", then assigns one-to-one. Only its confident lane resolves; a confident "none" does not. Header text and a locally computed value profile are all it receives — never a cell value.
lane: execute · RANKER_PROFILE_ROWS = 40self-hosted model · metered per column resolved
- Layer 4
Semantic
Embeddings cosine over the header against each field’s label + hints. Cached, and silently skipped when no embedding key is configured.
SEMANTIC_THRESHOLD = 0.78cheap · cached
- Layer 5
LLM
Every header still unresolved goes into ONE collision-aware batched call, constrained to your column set and told which fields are already claimed.
1 call per file · llmMatchHeadersBatch()metered
Contests are settled by quality, not column order: layers 1–3 all propose, the engine arbitrates once on (layer rank, confidence, column index), then runs layer 4 only for what is still unresolved. A target field, once assigned, cannot be claimed twice — you can’t emit two source columns into one field. How the cascade works →
Structural understanding
Real-world grids are messy. The engine reads them before it matches.
Before a single header is compared, a deterministic pass (lib/structure.ts) turns spreadsheets-as-humans-made-them into clean logical tables. No LLM, no configuration — just structure recovery.
Headers that aren’t on row 0
Multi-row & grouped headers
Unit rows captured
Transposed sheets un-pivoted
Reshape: beyond renaming
When mapping isn’t enough, the engine reshapes — safely.
Some inputs need more than column renaming: a pivot, a split, a join across sheets. A router picks the cheapest strategy that works, and the model — when one is used at all — never sees your data.
direct
plan
code
Across every generated path the boundary holds: the model is shown a skeleton — headers plus a clamped preview — and writes a transform it never gets to run against your full records. See how reshape works →
Two modes, one boundary
Schema-only, or full-data under a BAA — the boundary is one code path.
Both modes run the same in-process cascade. The only difference is what may leave you, and both are gated at a single clamp — not scattered across routes.
| Schema-only | Full-data | |
|---|---|---|
| What leaves you | Headers + ≤3 rows, 80 chars a cell. | The same clamp, plus the surviving cells on the layer-5 call. |
| Where it runs | Entirely in-process — the deterministic cascade never phones out. | In-process, except the one layer-5 call, under X-PHI + X-Region. |
| What it takes | Nothing — a DPA is usually not needed (provided on request). | A PHI entitlement and a BAA accepted in-app. |
| The clamp | clampForSchemaOnly() at the HTTP edge of every route. | The same function, the same code path. |
The API
Every surface is a REST route you can read.
No SDK required and no bespoke verbs: the engine is a dozen routes under /v1, each doing one thing. These are the paths as they exist in the codebase.
- POST
/v1/uploadsStage a CSV, Excel, JSON or SQL export. Returns an upload id and expires_at — the bytes live in KV for your workspace retention window (24h by default). - POST
/v1/uploads/{id}/matchRun the six-layer cascade over the file’s headers against a template. - POST
/v1/uploads/{id}/match/streamThe same run as Server-Sent Events — a frame per cascade layer.streams - PATCH
/v1/uploads/{id}/mappingsOverride a proposed mapping. Medium- and high-risk templates come back with requires_hitl. - POST
/v1/uploads/{id}/commitConfirm the mapping. Feeds layer 1 and records the layout fingerprint for one-click reuse. - POST
/v1/matchHeaders plus ≤3 clamped sample rows, no key required. Layers 1–4 only — an anonymous call can never reach the metered layer.public - POST
/v1/reshapePivot, split, join across sheets. The router picks direct, plan or code, and echoes which it chose. - POST
/v1/convertAny-to-any import: images, PDF, audio, HTML and URLs into structured rows. - POST
/v1/layouts/lookupReuse the last confirmed mapping for an identical header row — a fingerprint cache hit, no AI. - POST
/v1/layouts/driftDiff a new field set against the newest confirmed layout: stable, first_seen, or drift with an added/removed diff. - GET
/v1/templates33 templates across 7 packs, with their fields, multilingual hints and validators. - GET
/v1/usageMetered usage for the workspace; ?phi=1 adds token consumption and cost.
The public host is api.adaptivmapr.com. Dashboard-session routes live under /v1/me/* and are CSRF-gated. Full API reference →
For developers
One call, and the layer is in the answer.
Every match carries the layer that produced it and the confidence it produced it at. You can prove which columns cost nothing — and exactly which ones reached the metered layer.
- REST /v1 — uploads, match, mappings, commit, reshape, convert, layouts, templates, usage
- HMAC self-contained keys — a key verifies without a database lookup; revocation is a check layered on top
- SSE progress — /match/stream and /me/reshape/stream emit a frame per phase, then the same terminal JSON
- MCP — adaptivmapr.match_headers calls POST /v1/match, clamped to the schema-only contract at the edge; free layers 1–3 with no key, plus the ranker and AI (billed to the wallet) when a key is set
curl -s https://api.adaptivmapr.com/v1/match \
-H 'content-type: application/json' \
-d '{
"template_id": "lab_results_v1",
"headers": ["Patient-ID","LOINC","Wert","Einheit","Entnahme"],
"sample_rows": [["P-1042","2345-7","5.4","mmol/L","2026-03-11"]]
}'{
"template_id": "lab_results_v1",
"matches": [
{ "source_col": "Patient-ID", "target_field": "patient_id", "confidence": 1, "source": "heuristic" },
{ "source_col": "LOINC", "target_field": "loinc_code", "confidence": 0.85, "source": "heuristic" },
{ "source_col": "Wert", "target_field": "value", "confidence": 1, "source": "heuristic" },
{ "source_col": "Einheit", "target_field": "unit", "confidence": 1, "source": "heuristic" },
{ "source_col": "Entnahme", "target_field": "taken_at", "confidence": 1, "source": "heuristic" }
],
"auto_accept_threshold": [
{ "minN": 100, "minRatio": 0.95 },
{ "minN": 20, "minRatio": 1 }
],
"cascade_layers": ["statistics","heuristic","fuzzy","ranker","semantic","ai"],
"unmapped": []
}Built for regulated data
Compliance isn’t a mode — it’s the substrate.
The cascade is the product, but the deployed engine is a full stateful SaaS with the controls regulated teams have to answer for. These are wired in, not roadmap.