Docs · Quickstart
Your first import, in five calls.
POST a file, let the five-layer cascade propose mappings, confirm them, and commit. Schema-only mode keeps raw records on your side; full-data routes the one AI call in-region under a BAA. Every map draws down a prepaid token wallet.
The whole flow
Five calls, start to finish
Each step below is one of these. The pill is the scope your key must carry — mint it with commit, or steps 4 and 5 come back 403.
- GET
/v1/templatesConfirm the key works and pick a target template.read - POST
/v1/uploadsParse the file. Returns an upload_id and the detected columns.transform - POST
/v1/uploads/:id/matchRun the cascade against the template you picked.read - PATCH
/v1/uploads/:id/mappingsConfirm the proposals you trust; correct the rest.commit - POST
/v1/uploads/:id/commitValidate every row and hand back the mapped result.commit
Everything below is these five calls with their bodies spelled out. If one of them refuses, the error names itself: the error reference lists the twelve codes worth branching on, and troubleshooting covers the failure modes that look like bugs and are not.
Step 1
Get an API key
Mint a key in the dashboard after you top up a prepaid token wallet ($10 minimum, shared across the phi-cloud suite). Keys are HMAC-signed and self-contained — there is no shared session store — and revocation is enforced by a server-side check on every request that fails closed. Pass the key as a bearer token in the Authorization header on every call.
Ask for the right scopes at mint. A key created without an explicit scope list gets read, transform and validate only. Steps 4 and 5 below need commit; without it they return 403 insufficient_scope, and the body names the scope that was missing in required_scope. A read-only listing call is the quickest way to prove the key works:
curl https://api.adaptivmapr.com/v1/templates \
-H "Authorization: Bearer $ADAPTIVMAPR_API_KEY"33 pre-built templates ship in the catalogue — 10 healthcare (FHIR-aware) plus Core, CRM, E-commerce, Finance & Payments, HR & People and Evidence. Pick the one whose shape matches your data, register your own under /v1/schemas, or browse the catalogue.
Step 2
POST your file
Send the file to POST /v1/uploads as multipart under the field name file. The parser accepts CSV, TSV, XLSX and XLSM, JSON, XML and Parquet, plus the tables inside .docx and .pptx; header_row and sheet_name are the two optional form fields when the defaults guess wrong.
The target template is not chosen here. The upload call only parses — you name the template on the match call in step 3, which means you can inspect the detected columns first and pick accordingly.
curl -X POST https://api.adaptivmapr.com/v1/uploads \
-H "Authorization: Bearer $ADAPTIVMAPR_API_KEY" \
-F "file=@roster.csv"
# when the sheet opens with title rows, or the workbook has several tabs:
# -F "header_row=3" -F "sheet_name=Roster"{
"upload_id": "upl_8f3a92c1",
"filename": "roster.csv",
"format": "csv",
"detected_columns": ["Vorname", "Nachname", "Geb", "E-Mail"],
"row_count": 1244,
"sample_rows": [ /* ≤3 rows, ≤80 chars per cell */ ],
"warnings": [],
"expires_at": "2026-09-03T09:14:22.000Z"
}Form fields
| Field | Type | What it does | |
|---|---|---|---|
file | file (multipart) | required | The file itself, under the field name file. Missing or not a file → 400 file_missing; over the 10 MiB cap → 413 file_too_large, with max_bytes in the body. |
header_row | integer | default 0 | Which row holds the headers. Raise it when a sheet opens with a title block or metadata rows. |
sheet_name | string | optional | Which worksheet to parse. Defaults to the first one in the workbook. |
multipart/form-data. Let your HTTP client set the Content-Type — a hand-written boundary is the most common reason this call 400s.
Prefer not to send a file at all? POST /v1/match takes just the headers plus ≤3 sample rows (≤80 characters each) and is stateless — that is all that leaves your environment, and it needs no key.
Step 3
The cascade proposes mappings
Run the match against your upload_id, naming the template in target_schema.schema_id. Five layers fire cheapest-first — statistics → heuristic → fuzzy → semantic → LLM — and the moment a layer auto-accepts a column, the layers below it never run. Keep mode on "schema-only" to keep raw records on your side; "full-data" needs an active PHI entitlement, and without one the call returns 402 phi_gateway_required.
POST /v1/uploads/upl_8f3a92c1/match
{ "target_schema": { "schema_id": "patient_demographics_v1" },
"mode": "schema-only",
"use_ai": true }Request body
| Field | Type | What it does | |
|---|---|---|---|
target_schema.schema_id | string | required | A plain template id — patient_demographics_v1. There is no sch_ prefix and nothing to look up first. Unknown → 404 template_unknown; absent → 400 schema_id_required. |
mode | "schema-only" | "full-data" | default schema-only | full-data needs an active PHI entitlement; without one you get 402 phi_gateway_required. |
use_ai | boolean | optional | Set false to stop the cascade at layer 4. Columns that would have reached the metered call come back unmapped instead of billed. |
phi_mode | boolean | workspace default | Per-run routing override, falling back to the workspace setting, which defaults to off. PHI routing adds 20% and needs an accepted BAA, or you get 403 agreement_required. |
The response is one proposal per detected column, each tagged with the source layer that produced it — statistics · heuristic · fuzzy · semantic · ai · unmapped — plus a confidence, the reasoning, the unmapped list, and the auto_accept_threshold rules the statistics layer applied.
{
"upload_id": "upl_8f3a92c1",
"template_id": "patient_demographics_v1",
"mode": "schema-only",
"matches": [
{ "source_col": "Vorname",
"target_field": "first_name",
"confidence": 1.0,
"source": "heuristic",
"reasoning": "matched DE hint 'vorname'" }
],
"unmapped": ["Geb"],
"auto_accept_threshold": [ { "minN": 100, "minRatio": 0.95 },
{ "minN": 20, "minRatio": 1.0 } ],
"cascade_layers": ["statistics","heuristic","fuzzy","semantic","ai"]
}Step 4
Confirm or adjust
PATCH /v1/uploads/:id/mappings takes an array of {source_col, target_field, user_confirmed} overrides. Confirm the proposals you trust and correct the rest; corrections feed the statistics layer, so the same header resolves without AI next time — for this workspace only.
curl -X PATCH https://api.adaptivmapr.com/v1/uploads/upl_8f3a92c1/mappings \
-H "Authorization: Bearer $ADAPTIVMAPR_API_KEY" \
-H "Content-Type: application/json" \
-d '[{ "source_col": "Geb", "target_field": "date_of_birth", "user_confirmed": true }]'One override
| Field | Type | What it does | |
|---|---|---|---|
source_col | string | required | A column from detected_columns, exactly as the parser read it. A name that is not in the upload is ignored rather than erroring. |
target_field | string | null | required | The field to map it onto, or null to leave the column unmapped on purpose. |
user_confirmed | boolean | default false | Only a confirmed override teaches the statistics layer. Sending a correction without it changes this run and nothing after it. |
The body is an ARRAY of these — send every column you want to change in one call, not one request per column.
For a medium- or high-risk template — patient_demographics_v1, lab_results_v1, claims_line_items_v1, payments_v1, employees_v1 and the rest — the response sets requires_hitl: true and hitl_status: "pending_review" so you can gate your own commit workflow behind a human review. The flag is advisory in v1: it does not block the commit server-side.
{
"upload_id": "upl_8f3a92c1",
"mappings": [ /* ... */ ],
"requires_hitl": true,
"hitl_status": "pending_review"
}Step 5
Commit and use the rows
POST /v1/uploads/:id/commit validates every row against the template’s field validators and returns the mapped, validated rows inline (≤10k rows). Pass skip_invalid_rows to drop failures. Three delivery targets combine: inline rows, a webhook object ({url, secret}) that streams batches of 500 signed with an X-Mapr-Signature HMAC-SHA256 header — required above 10k rows — and a database object naming a destination connector.
output selects the shape: rows (the default), fhir, or a serialized file (csv, tsv, xml, sql, json, xlsx, parquet). The commit metadata always survives — the file rides inside the JSON envelope rather than replacing it.
curl -X POST https://api.adaptivmapr.com/v1/uploads/upl_8f3a92c1/commit \
-H "Authorization: Bearer $ADAPTIVMAPR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "skip_invalid_rows": true }'{
"committed": true,
"upload_id": "upl_8f3a92c1",
"template_id": "patient_demographics_v1",
"accepted": 1238,
"skipped": 6,
"mapping_fingerprint": "b91c…",
"delivery": "inline",
"delivery_mode": "inline",
"rows": [ /* the validated rows */ ]
}Request body
| Field | Type | What it does | |
|---|---|---|---|
skip_invalid_rows | boolean | default false | Drop failing rows and report them in skipped. Without it, one bad row fails the whole commit with 422 validation_failed. |
output | "rows" | "fhir" | file format | default rows | csv, tsv, xml, sql, json, xlsx, parquet. Anything else → 400 invalid_output. |
webhook | { url, secret } | optional | Signed batches of 500 to your endpoint. Required above 10 000 accepted rows — inline over that returns 413 inline_too_large. |
database | { connector_id, table?, on_conflict?, dry_run? } | optional | Write straight into a saved destination connector. Credentials come from the connector, never from this body. |
Every field is optional — an empty body commits the confirmed mapping as inline rows.
Every commit is hashed and written to the append-only audit trail with the mapping fingerprint and the accepted/skipped counts, so you can reconstruct exactly which rows landed from which file. The whole confirmed mapping is cached as a layout at the same moment, so the next file with this header row can be re-mapped with POST /v1/layouts/lookup and no AI at all.
Next
Keep going — put it in production.
The reference covers every /v1 route and its scope, the error codes worth handling, PHI routing and the BAA gate, connectors and signed webhooks, and the MCP server for Cursor and Claude Desktop.