Capability · Layouts
Map it once. Never again.
The same sender, every month, in the same shape. Map it once — after that it is one click and no AI at all.
- To replay a known sender
- One click
- Added and removed columns
- Named
The job
You already mapped this file. Last month.
What changes
What you get
Not a demo. The thing that runs every day.
Reuse
One lookup, zero AI
When a header row you have confirmed before comes back, the stored mapping is offered outright. No cascade run, no embeddings, no model call.
HowPOST /v1/layouts/lookup returns { found: true, mapping, source_headers, use_count, last_used_at } for a (workspace, template, fingerprint) hit, straight out of public.mapping_layouts.
Fingerprint
The same normalizer the cascade uses
“E-Mail” and “email” fingerprint alike, because the hash is taken after the cascade’s own normalization — not over the raw text.
HowfingerprintHeaders() is sha256(headers.map(normalize).join('|')) over Web Crypto — deterministic in both the Worker and Node, and reproducible outside our system if you want to check it.
Drift
Three answers, no model
Ask about an incoming header row and you get stable, first_seen or drift — with an added / removed / common diff against the layout that source used most recently.
HowdetectDrift() in lib/layouts.ts baselines against the most recently USED confirmed layout for the pair and returns diff, baseline_use_count and baseline_last_used_at. Read-only and LLM-free — safe to call on every import.
Precision
A reorder is reported as a reorder
A source that shuffles its columns is not the same event as a source that adds one, and a guardrail that cannot tell them apart is a guardrail you turn off.
HowThe fingerprint is order-SENSITIVE, so a reordered row is a new fingerprint — but reordered_only: true then says the field SET is unchanged and only the order moved. first_seen is reported for a pair that has no confirmed layout yet, rather than crying drift.
Automatic
You never curate the library
Commit records the layout for you. Every confirmation quietly makes the next identical import a lookup instead of a cascade run.
HowThe commit path records automatically, and POST /v1/layouts/record exposes the same write for an importer that runs its own review UI. use_count is bumped on every reuse.
Scoped
Workspace-scoped, never shared
Your header fingerprints and confirmed mappings belong to your workspace. Reuse only ever offers something your own workspace confirmed.
Howmapping_layouts rows are keyed by (tenant_id, template_id, header_fingerprint) and every read is tenant-scoped. There is no cross-tenant layout sharing, in either direction.
Reference
What drift detection returns
The same file lands every week. There is no reason to re-run the cascade — let alone the AI — when the header row is one you have already confirmed. Every confirmed mapping is stored against a SHA-256 fingerprint of its normalized header row, so a repeat import is a lookup. The same fingerprint answers the other question: has this source quietly changed?
- 01
Normalize, then hash
fingerprintHeaders()issha256(headers.map(normalize).join('|'))over Web Crypto — the same normalizer the heuristic layer uses, so “E-Mail” and “email” hash alike. - 02
Look up the triple
(tenant_id, template_id, header_fingerprint)againstpublic.mapping_layouts. A hit is the whole confirmed mapping, offered as-is — no cascade, no embeddings, no model. - 03
Or diff against the baseline
No exact hit? Drift baselines against the most recently USED confirmed layout for the pair and returns added / removed / common — plus reordered_only when only the order moved.
- 04
Record on confirm
Commit writes the layout for you and bumps use_count on every reuse. You never curate the library; confirming is what fills it.
| status | When you get it | What comes with it |
|---|---|---|
| stable | This exact fingerprint is already confirmed for the pair | header_fingerprint |
| first_seen | No confirmed layout exists for this workspace + template yet | header_fingerprint |
| drift | Prior layouts exist, but this shape is new | baseline_fingerprint · baseline_use_count · baseline_last_used_at · reordered_only · diff{added, removed, common} |
Try it
Ask before you map
Two calls, both pure lookups. One asks whether this exact header row already has a confirmed mapping; the other asks whether a known source has changed shape since you last confirmed it.
# reuse — does this exact header row already have a confirmed mapping?
curl https://api.adaptivmapr.com/v1/layouts/lookup \
-H "Authorization: Bearer $MAPR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "template_id": "patient_demographics_v1",
"headers": ["Nachname", "Vorname", "Geburtsdatum", "E-Mail"] }'
# drift — has the field set for this known source changed?
curl https://api.adaptivmapr.com/v1/layouts/drift \
-H "Authorization: Bearer $MAPR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "template_id": "patient_demographics_v1",
"headers": ["Nachname", "Vorname", "Geburtsdatum", "E-Mail", "Geschlecht"] }'# lookup → the confirmed mapping, reusable as-is
{ "template_id": "patient_demographics_v1",
"header_fingerprint": "b91c4e…",
"found": true,
"mapping": { "Nachname": "last_name", "Vorname": "first_name",
"Geburtsdatum": "date_of_birth", "E-Mail": "email" },
"use_count": 27, "last_used_at": "2026-08-25T02:00:11Z" }
# drift → one field appeared since the last confirmed layout
{ "template_id": "patient_demographics_v1",
"status": "drift",
"header_fingerprint": "0af71d…",
"baseline_fingerprint": "b91c4e…", "baseline_use_count": 27,
"reordered_only": false,
"diff": { "added": ["Geschlecht"], "removed": [],
"common": ["nachname", "vorname", "geburtsdatum", "email"] } }- Reuse and drift are pure fingerprint lookups: no cascade, no embeddings, no LLM, and therefore no token cost. A re-map still draws the flat per-map fee — the point is to make a recurring import instant, not free.
- The fingerprint is order-sensitive by design, so a shuffled header row is a new layout — but
reordered_onlytells you it was only a shuffle. - A recurring connector pairs naturally with this: the nightly file has the same header row every night, so it re-maps on a lookup rather than a cascade run.
The surface
Every route this page actually has.
Three pure lookups and the commit that fills the library. None of them calls a model, so all three are safe to put in front of every import you run.
- POST
/v1/layouts/lookupDoes this exact header row already have a confirmed mapping? Returns the mapping, use_count and last_used_at.read scope - POST
/v1/layouts/driftstable / first_seen / drift for a known workspace + template pair, with the added / removed / common diff.read scope - POST
/v1/layouts/recordWrite a confirmed layout yourself — for an importer that runs its own review UI instead of our commit path.commit scope - POST
/v1/uploads/{id}/commitRecords the layout automatically. This is how the library fills up without anyone curating it.commit scope - GET
/v1/me/layoutsThe stored layouts for this workspace, as the dashboard lists them.dashboard only
Limits & failure modes
What it refuses to do — and the code it says it with.
A guardrail you have to interpret is a guardrail you turn off. These are the exact edges — including the two cases that look like drift and are not.
found: falseThis exact fingerprint has no confirmed mapping yet.
Not an error. Run the cascade, confirm it, and the next identical header row is a lookup.
status: first_seenThe workspace + template pair has no confirmed layout at all.
Reported as first_seen rather than as drift — crying drift on a source you have never confirmed is how a guardrail loses its audience.
reordered_only: trueThe field SET is unchanged and only the column order moved.
The fingerprint is order-SENSITIVE, so a shuffle really is a new layout. This flag is what stops a reorder reading as an added field.
400 too_many_headersMore than 200 headers on any of the three routes.
The same cap the cascade uses. A header row past 200 columns is usually a transposed sheet — reshape it first.
400 invalid_targetA recorded mapping names a target that is not a field of that template.
The refusal names the source column and the value, so a bad write cannot poison the library the reuse path trusts.
Workspace-scopedYou expect a layout another tenant confirmed.
There is no cross-tenant layout sharing, in either direction. Reuse only ever offers something your own workspace confirmed.
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.
Lookup & drift
No token cost
Pure fingerprint reads: no cascade, no embeddings, no LLM. Cheap enough to run as a standing guardrail in front of a nightly connector sync.
A reused mapping
$0.001
A re-map still draws the flat per-map fee — the point of reuse is to make a recurring import instant and AI-free, not free.
What you stop paying
The AI layer
A cache hit never reaches the metered layer-5 call, so the token line on a recurring import goes to zero and stays there.
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.
How does reuse know it is the same file?
What does drift detection actually return?
Does reuse cost anything?
Can another tenant see my layouts?
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.