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.

No free tier · $10 minimum top-up · a few tokens per map

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:

bash
curl https://api.adaptivmapr.com/v1/templates \
  -H "Authorization: Bearer $ADAPTIVMAPR_API_KEY"
→ 200 · 33 templates across 7 packs · public, edge-cached

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"
201 · created
{
  "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"
}
→ stored in Cloudflare KV with a 24-hour TTL, then it expires on its own

Form fields

FieldTypeWhat it does
filefile (multipart)requiredThe 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_rowintegerdefault 0Which row holds the headers. Raise it when a sheet opens with a title block or metadata rows.
sheet_namestringoptionalWhich 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.

http
POST /v1/uploads/upl_8f3a92c1/match
{ "target_schema": { "schema_id": "patient_demographics_v1" },
  "mode": "schema-only",
  "use_ai": true }

Request body

FieldTypeWhat it does
target_schema.schema_idstringrequiredA 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-onlyfull-data needs an active PHI entitlement; without one you get 402 phi_gateway_required.
use_aibooleanoptionalSet false to stop the cascade at layer 4. Columns that would have reached the metered call come back unmapped instead of billed.
phi_modebooleanworkspace defaultPer-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.

json
{
  "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"]
}
→ every column resolved by layers 1–3 cost nothing; only what falls through reaches the metered call

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.

bash
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

FieldTypeWhat it does
source_colstringrequiredA column from detected_columns, exactly as the parser read it. A name that is not in the upload is ignored rather than erroring.
target_fieldstring | nullrequiredThe field to map it onto, or null to leave the column unmapped on purpose.
user_confirmedbooleandefault falseOnly 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.

json
{
  "upload_id": "upl_8f3a92c1",
  "mappings": [ /* ... */ ],
  "requires_hitl": true,
  "hitl_status": "pending_review"
}
→ hitl_status is 'not_required' on a low-risk template

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.

bash
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 }'
200 · committed
{
  "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 */ ]
}
→ a non-empty failed_batches means a PARTIAL delivery — a 200 is not proof of full delivery

Request body

FieldTypeWhat it does
skip_invalid_rowsbooleandefault falseDrop failing rows and report them in skipped. Without it, one bad row fails the whole commit with 422 validation_failed.
output"rows" | "fhir" | file formatdefault rowscsv, tsv, xml, sql, json, xlsx, parquet. Anything else → 400 invalid_output.
webhook{ url, secret }optionalSigned 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? }optionalWrite 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.

Prepaid token wallet · schema-only data-minimization mode · no free tier