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.

Importreshape.pyGenerate codeworkbook.xlsxClean output

The cascade

Five 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 files are fully mapped before the paid layer is ever touched. That early exit is the single biggest cost lever in the system.

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

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

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

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

  5. 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 →

  • No raw records to an LLM
  • HMAC self-contained API keys
  • RLS tenant isolation
  • Uploads expire from KV in 24 h
  • In-region PHI routing under a BAA

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

Detects the real header row under title banners, logos, and blank lead-in rows — no “skiprows” guesswork required.

Multi-row & grouped headers

Flattens stacked header bands into a single logical name, joining a group and its child — “Instrument › Variable” — so each column reads cleanly.

Unit rows captured

Recognizes a units row beneath the header and carries it as column metadata instead of poisoning the first data row.

Transposed sheets un-pivoted

Auto-detects wide → long layouts where variables run down a column, and unpivots them back into tidy rows — deterministically, no LLM.

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.

Strategy

direct

The plain cascade. Columns map one-to-one; nothing to reshape. Free and deterministic.

How · No program is authored at all — the mapping IS the transform.

Strategy

plan

A declarative JSON DSL run in-process — deterministic and byte-reproducible. Same input, same output, every time. No code execution, no LLM.

How · Re-run a plan and you get identical bytes back.

Strategy

code

Generated Python / R / SQL run in an isolated sandbox with no network. The model sees only the table shape and a clamped preview — never the full dataset.

How · Every prompt carrying customer content is fenced; the generated program runs behind network isolation.

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-onlyFull-data
What leaves youColumn headers + up to 3 sample rows, each clamped to ≤80 characters. Raw records stay home.The same clamped preview for matching — plus, for the layer-5 call only, the cells that fell through to the LLM.
Where it runsEntirely in-process. The deterministic cascade never phones out.In-process cascade; the single layer-5 LLM call routes to a PHI-eligible, in-region provider under X-PHI + X-Region.
ComplianceData-minimized — only clamped previews leave you, so a DPA often isn’t needed (provided on request).Active PHI entitlement, plus a BAA accepted in-app. In-region, PHI-eligible model enforced at the gateway.
One clampclampForSchemaOnly() is the single edge chokepoint every route shares.Same clamp for matching; the data boundary is one code path, not per-route promises.

Schema-only is a data-minimization mode — only clamped previews leave you. Full-data is gated behind an active PHI entitlement and an accepted BAA; only the layer-5 LLM call routes to a PHI-eligible provider, in-region.

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; the bytes live in KV for 24 hours.
  • POST/v1/uploads/{id}/matchRun the five-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. Deterministic layers only — the metered layer is never reachable anonymously.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 — mapr.match_headers calls POST /v1/match, clamped to the schema-only contract at the edge
POST /v1/match · bash
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"]]
  }'
200 OK
{
  "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","semantic","ai"],
  "unmapped": []
}
→ 5 of 5 headers mapped on layer 2 · 0 sent to an LLM · 0 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.

Hash-chained audit trail

Every commit and confirmation is written to a tamper-evident log (Chainlog) and mirrored to the Supabase audit_logs table — a replayable record of who mapped what.

How · mapping.commit carries review_approved and the attempt count.

24-hour upload TTL

Uploaded files live in Cloudflare KV with a 24h expiry, then vanish. Nothing lingers longer than the mapping session needs.

How · lib/uploads.ts — KV write with a 24h TTL, mirrored to Supabase.

KEK-encrypted secrets

Connector credentials and other secrets are envelope-encrypted under a key-encryption key, scoped per tenant and never returned in plaintext.

How · GET /v1/connectors/{id}/secret-meta returns metadata, never the secret.

Strict CSP, per-request nonce

Every page ships a strict Content-Security-Policy with a fresh nonce minted per request — inline scripts that aren’t ours simply don’t run.

How · middleware.ts mints x-nonce; every JSON-LD block on this page carries it.

Fail-closed PHI allowlist

Full-data PHI traffic only reaches BAA-covered hosts on an explicit allowlist. If it’s unset in production, PHI routing refuses rather than leaks.

How · MAPR_PHI_ALLOWED_HOSTS — /api/health reports the component down when unset.

HIPAA-ready, GDPR & nFADP posture

Regulated-data-first by design: schema-only mode ships no identifiers, full-data routes in-region under a BAA, and residency is enforced at the edge. SOC 2 is in progress.

How · BAA + confidentiality terms are accepted in-app and recorded in tenant_agreements.

Ready when you are

One key unlocks the whole engine.

Schema-only mode runs the same cascade on your prepaid wallet — every map is a few tokens; only files that need the paid layer add AI token cost.

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