AdaptivMapr

Solution · HR & people

Employee data moves. Nobody sees it.

Salaries and national IDs in a spreadsheet, going from one vendor to another. The mapping happens without a human reading the rows.

Characters per sample cell
≤ 80
Every commit
Audited
The pack

HR & People

templates
4templates
gate on review
3gate on review
field validators
4field validators
hint languages
5hint languages
employees_v1payroll_v1time_off_v1candidates_v1

The job

The file you are not comfortable emailing anyone.

An HRIS migration, a payroll cutover, an ATS export. The file holds salaries, national IDs and home addresses, and mapping it means somebody opens it — or pastes a sample somewhere to ask what a column means. One wrong mapping exposes a pay band across a company.

What changes

Schema-only mode resolves the file from its column names and at most three clamped sample rows, so the salaries are never in the request at all. The mapping is scored, reviewable, and audited.

What actually arrives

Every source exports it differently.

HRIS, ATS and payroll vendors each export employee, payroll and candidate data differently, and one wrong column mapping exposes salaries or national IDs. Schema-only mapping resolves those files from headers and three clamped sample rows — for most HR exports, not a single complete employee record is ever transmitted.

Four shapes cover most people-data onboarding. Every one of them is the most sensitive file its owner will send you all year.

HRIS employee master export

CSV or XLSX, one row per employee

Personalnummer, matricule, matricola or Employee No for the same key; national ids in country-specific formats; a status column whose values differ per vendor — active / Aktiv / 1.

Payroll run

CSV or XLSX, one row per employee per period

Gross and net in adjacent, similarly named columns; the pay period as a month name, a date or a period code; payout IBANs pasted with spaces from the bank portal.

ATS pipeline export

CSV or JSON, one row per candidate

Free-text stage names that mean the same thing across three recruiters; phone numbers in local format without a country code; a single Name column where the template expects a full name.

Leave / absence register

XLSX, often one sheet per year

Half-days as 0.5 in one column and as a separate flag in another; start and end dates in two formats; absence types written in the local language.

4 templates

HR & People — every field carries DE / FR / IT / EN / ES hints.

3 gate on review

1 medium + 2 high — PATCH /mappings returns requires_hitl: true.

4 field validators

iban, email, phone, regex

  • Headers + ≤3 rows, 80 chars each
  • Layers 1–3 need no model
  • requires_hitl on medium + high
  • PHI pinned in-region under a BAA
  • Per-workspace, never cross-tenant

How it works

Four calls, and the file is in your schema.

Upload, cascade, confirm, commit. The deterministic layers do most of the work before anything metered runs, and nothing but headers and a few clamped sample rows leaves your system on the way.

  1. 1Step 1

    Upload the HR export

    An employee master file, a payroll run, a leave register or an ATS pipeline export. Schema-only sends the headers and up to three sample rows clamped to 80 characters each — the rest of the file never leaves.

    POST /v1/uploadsschema-onlyPII stays in-house
  2. 2Step 2

    The cascade maps to an HR template

    employees_v1, payroll_v1, time_off_v1 or candidates_v1. Personalnummer, matricule, matricola and número de empleado all resolve to employee_id on the free heuristic layer, so vendor-specific header names cost nothing to absorb.

    employees_v1payroll_v1time_off_v1candidates_v1
  3. 3Step 3

    Validate what the template constrains

    Payout IBANs on employees_v1 and payroll_v1 run the mod-97 checksum; national_id is regex-constrained; candidate email and phone are format-checked. A malformed value fails at map time with its own error code.

    ibanemailphoneregex
  4. 4Step 4

    Review, commit, and keep the record

    employees_v1 and payroll_v1 are high-risk and candidates_v1 is medium, so the mappings response comes back flagged for review. Every confirmation and commit is written to the audit log with the mapping fingerprint.

    requires_hitlaudit_logsmapping.commit

Field level

One template, printed in full.

The highest-risk template in the HR pack, printed field by field. Every row below is a field object the catalogue ships, with the validator ids that run on it and the header vocabulary the free heuristic layer already recognises.

employees_v1high8 fields · 2 required · 3 validated
FieldTypeRequiredValidatorsHeader vocabulary it already knows
employee_idstringrequired—personalnummer · matricule · matricola · employee id +1
legal_namestringrequired—name · nom · nome · legal name +1
emailemail—emailemail · e-mail · mail · correo +1
national_idstring—regexausweisnummer · ahv · numéro national · codice fiscale +2
ibanstring—ibaniban · compte · konto · bank account
hire_datedate——eintrittsdatum · date d'embauche · data di assunzione · hire date +1
departmentstring——abteilung · département · dipartimento · department +1
statusenum (3)——status · statut · stato · estado
Every row is a field object from GET /v1/templates/employees_v1 — not a description of one. The hint lists are what the heuristic layer compares a header against after normalize() strips accents, punctuation and whitespace, which is why adding a hint is the highest-leverage change in the whole engine: it costs nothing to run and removes a model call.

Why AdaptivMapr

Built for the shape of regulated data.

What the hr & people pack gives you that a generic importer does not — and, under each card, the mechanism that does it.

Most HR files map without exposing a record

The cascade reads headers, not people. In schema-only mode a payroll file with 4,000 rows sends its column names and three truncated sample rows — salaries, IBANs and national IDs stay where they are.

HowclampForSchemaOnly() in lib/parser.ts is the single chokepoint: ≤3 rows, ≤80 characters per cell. It is applied at the HTTP edge in every /v1 route that accepts sample rows, so schema-only and full-data cannot drift apart.

The two most sensitive templates are the two flagged highest

employees_v1 and payroll_v1 hold national IDs, salaries and bank details, and they are the only HR templates rated high — so a mapping against either is flagged for review on every call, without configuration.

Howrisk: "high" on both; candidates_v1 is medium; time_off_v1 is low and commits straight through. PATCH /mappings derives requires_hitl from that field. The review queue is yours — a native one is roadmap, not v1.

Recurring imports stay cheap on purpose

Statistics, heuristic and fuzzy are pure compute. When a column resolves on one of them the metered model is never called — and a confirmed mapping teaches the statistics layer, so the same file gets cheaper over time.

HowAuto-accept rules {minN:100, minRatio:0.95} and {minN:20, minRatio:1.00} promote a learned pair to layer 1. Fuzzy auto-accepts at 0.80 on a token-set + Levenshtein score. Neither touches a model.

Every commit leaves a record

Confirmations and commits are written to the audit trail with the mapping fingerprint, so an HR or compliance review has a per-mapping history without you instrumenting anything.

Howmapping.confirm and mapping.commit events land in the audit_logs table, scoped to the workspace. DELETE /v1/me/workspace destroys the tenant row and its cascade in one implementation, with the audit event written first.

The frame

What applies here, and what we actually do about it.

Each frame below is paired with a mechanism, not an adjective. Where we hold nothing, the row says so — a compliance page that overstates is worse than one that is short.

GDPR / nFADP — employee data

Employee records are personal data processed under an employment relationship, and Art. 5(1)(c) still applies: process the minimum necessary for the purpose.

Our posture · The cascade reads headers, not people. Schema-only sends column names plus up to three sample rows clamped to 80 characters — for most HR exports no complete employee record is ever transmitted. One chokepoint, clampForSchemaOnly() in lib/parser.ts, enforces it at the HTTP edge. A DPA is provided on request.

Right to erasure

A processor has to be able to destroy what it holds, completely, and show that it did.

Our posture · DELETE /api/v1/me/workspace and the account-deletion route run the ONE implementation in lib/workspaceErasure.ts: audit event first, then the database cascade, the upload-store purge and the in-process cache clears. Neither route can report success when the tenant row was not actually deleted.

Isolation

Salary data from one workspace must not be able to influence — or be visible to — another.

Our posture · Parsed uploads live in Cloudflare KV under the workspace's retention window (24h by default, up to 30d), scoped by tenant; the dashboard plane runs under Supabase row-level security keyed to auth.users.id → tenants.id. Learned mapping statistics and cached layouts are workspace-scoped and never shared across tenants.

Auditability

An HR or works-council review asks who mapped what, when, and to which field.

Our posture · Confirmations and commits are written to the audit trail with the mapping fingerprint, scoped to the workspace, without you instrumenting anything. The medium and high templates additionally return requires_hitl so a mapping cannot quietly commit unreviewed inside your own workflow.

What we do not claim: SOC 2 is in progress, we hold no HR-specific certification, and AdaptivMapr is not an HRIS, a payroll bureau or a hosted onboarding UI in place of Flatfile. It is the mapping layer — the cascade, the templates, the validators and the delivery — under your own product or your own internal tooling.

Risk & review

The pack, printed.

Every template this vertical ships, with the risk level that decides whether a commit gates on human review.

Template idRiskCommit gateValidators
employees_v1highrequires_hitl: trueemail, regex, iban
payroll_v1highrequires_hitl: trueiban
time_off_v1lowrequires_hitl: false—
candidates_v1mediumrequires_hitl: trueemail, phone
Read from the live template catalogue — the same ids, risk levels and validator ids that GET /v1/templates returns. Nothing on this table is typed by hand.

The gate is a property of the template.

There is no setting for it and no per-id list to maintain. A template rated medium or high makes PATCH /mappings return requires_hitl: true and hitl_status: "pending_review" — on every call, for every workspace, derived in one line of the route from the risk field. AdaptivMapr sets the flag; your workflow owns the queue. A native review queue is on the roadmap, not in v1.

1 medium + 2 high

In this vertical, out of 4 templates.

13 flagged of 33

Across all 7 packs the engine serves on GET /v1/templates.

PATCH /v1/uploads/:id/mappings
[
  { "source_col": "Personalnummer",
    "target_field": "employee_id",
    "user_confirmed": true }
]
response · employees_v1
{
  "upload_id": "upl_7a41c2…",
  "mappings": [ … ],
  "requires_hitl": true,
  "hitl_status": "pending_review"
}
the body is an ARRAY of { source_col, target_field, user_confirmed } · 409 no_template if /match has not run on this upload yet

In code

A real call, end to end.

A German payroll run against payroll_v1. Five headers, five hits on the free heuristic layer — and because payroll is high-risk, the mappings response comes back flagged before anything can commit.

  • POST/v1/uploadsparse the file, return its headers + ≤3 clamped sample rows
  • POST/v1/uploads/:id/matchrun the six-layer cascade against one template
  • PATCH/v1/uploads/:id/mappingsrecord confirmations; returns requires_hitl
  • POST/v1/uploads/:id/validaterun the field validators over every row, indexed
  • POST/v1/uploads/:id/commitemit and deliver — inline, webhook or DB connector

Authentication is one header. Keys are HMAC-signed and carry their own tenant_id, so verifying one needs no database lookup — and a key can be scoped so the service that reads mappings is not the one allowed to commit them.

curl
# 1 · upload (schema-only: headers + <=3 clamped rows)
curl -s https://api.adaptivmapr.com/v1/uploads \
  -H "Authorization: Bearer $MAPR_KEY" \
  -F file=@lohnlauf.csv

# 2 · map to the HR template
curl -s https://api.adaptivmapr.com/v1/uploads/$ID/match \
  -H "Authorization: Bearer $MAPR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target_schema":{"schema_id":"payroll_v1"},
       "mode":"schema-only"}'

# 3 · confirm the mapping — high risk, so this gates
curl -s https://api.adaptivmapr.com/v1/uploads/$ID/mappings -X PATCH \
  -H "Authorization: Bearer $MAPR_KEY" \
  -H "Content-Type: application/json" \
  -d '[{"source_col":"Bruttolohn",
        "target_field":"gross_salary","user_confirmed":true}]'
response · PATCH /mappings
{
  "upload_id": "upl_7a41c2…",
  "mappings": [
    { "source_col": "Personalnummer", "target_field": "employee_id",  "user_confirmed": false },
    { "source_col": "Bruttolohn",     "target_field": "gross_salary", "user_confirmed": true  },
    { "source_col": "Währung",        "target_field": "currency",     "user_confirmed": false },
    { "source_col": "IBAN",           "target_field": "iban",         "user_confirmed": false },
    { "source_col": "Lohnperiode",    "target_field": "pay_period",   "user_confirmed": false }
  ],
  "requires_hitl": true,
  "hitl_status": "pending_review"
}
schema-only · headers + ≤3 rows clamped to 80 chars left your system · medium/high templates gate before commit

Rollout

From one file to a feed that runs itself.

Four phases, none of which requires the previous one to be finished. Most teams stop after the third and only wire the fourth when a customer starts sending the same file every month.

  1. 1

    Map an export without moving a record

    Run a real HRIS or payroll file in schema-only mode and read the per-column confidence. Nothing but the header row and three truncated sample rows leaves — which is usually the difference between “we can trial this” and “legal needs to look at it first”.

    POST /v1/uploadsmode: schema-only
  2. 2

    Wire the four calls

    uploads → match → mappings → commit under a scoped API key. Keys are HMAC-signed and carry their own tenant_id, so the service that confirms mappings can hold a different, narrower key from the one that reads them.

    Bearer mp_live_…commit scope
  3. 3

    Send the gate where approvals already live

    employees_v1 and payroll_v1 are high-risk and candidates_v1 is medium, so all three return requires_hitl on every call. Route it into your existing approval path; we set the flag and record the confirmation, your workflow owns the queue.

    requires_hitlaudit_logs
  4. 4

    Let the monthly run stop being a project

    Each confirmed mapping is cached against a fingerprint of the normalized header row and teaches the statistics layer, so the same vendor’s next file is a lookup. When the vendor changes a column, drift returns a deterministic added / removed / common diff instead of a silent mis-map.

    /v1/layouts/lookup/v1/layouts/drift

FAQ

The questions that decide it.

Does employee or salary data leave my system?
In schema-only mode, no complete record does. Only the column headers and up to 3 sample rows — each clamped to 80 characters — are sent, and the cascade maps on those alone. For most HR exports that means no full employee record is ever transmitted. Full-data mapping is a separate, explicit opt-in.
Which HR templates require review before commit?
employees_v1 and payroll_v1 are high-risk and candidates_v1 is medium, so all three return requires_hitl: true and hitl_status: "pending_review" from PATCH /v1/uploads/:id/mappings. time_off_v1 is low-risk and commits straight through. The flag is derived from the template risk level; your approval workflow decides what to do with it.
How does this keep recurring HR imports cheap?
Three of the five cascade layers cost nothing to run: statistics (learned from your own confirmations), heuristic (normalized comparison against the field name, label and its DE/FR/IT/EN/ES hints) and fuzzy (token-set plus Levenshtein, auto-accepting at 0.80). When a column resolves on one of those, the metered model is never called — that is the single biggest cost lever in the system. Every map still draws the small flat per-map fee.
Is there an audit record?
Yes. Confirmations and commits are written to the audit trail with the mapping fingerprint, scoped to your workspace, so an HR or compliance review has a per-mapping history without extra instrumentation. Deleting the workspace runs one erasure implementation — audit event first, then the database cascade, the upload-store purge and the cache clears.
Are our uploads isolated from other customers?
Yes, at two levels. Parsed uploads live in Cloudflare KV under the workspace retention window (24h by default, up to 30d) and are scoped by tenant; the dashboard plane runs on Supabase row-level security keyed to auth.users.id → tenants.id. Learned mapping statistics and cached layouts are workspace-scoped and never shared across tenants.
Can we run this with no AI in the loop at all?
Yes. Layers 4 and 5 are config-gated — the semantic layer needs an embeddings key, the LLM layer needs a phi-cloud key — and both fail soft to OFF when the key is absent. With neither configured only statistics, heuristic and fuzzy run, and anything unresolved comes back in unmapped for a person to assign. For a lot of HR teams that is the whole answer to “does our salary data go to a model”.
What happens when a mapping is wrong?
Every match carries a confidence and the layer it came from, so a weak pick is visible before commit. You override it in PATCH /v1/uploads/:id/mappings with user_confirmed: true; the confirmation teaches the statistics layer for your workspace, and when your choice differs from the suggestion the original is recorded as a negative signal so it is offered less readily next time. One target field can be claimed by only one source column per run, so two columns cannot both map to gross_salary.
Is this a replacement for our HRIS integration or for Flatfile?
No. AdaptivMapr is the mapping engine and its API — the six-layer cascade, the template catalogue, validators, layout reuse and delivery. It is not an HRIS, not a payroll system, and not a drop-in replacement for a hosted onboarding UI. Teams run it as the mapping layer underneath their own import experience.
What does it cost to onboard a vendor’s monthly file?
There is no free tier, no seats and no contract. AdaptivMapr runs on a prepaid token wallet with a ~$10 minimum, shared across the phi-cloud suite. Every map draws a small flat fee — a few tokens — including a fully deterministic map and a layout-cache hit that uses no AI at all. Metered AI work bills the tokens it consumes on top; a PHI-routed run adds 20% to the whole charge.

Map hr & people data without shipping raw records.

Start on schema-only — headers and a few clamped sample rows, a few tokens per map. Turn on PHI routing when you need it: in-region, under a BAA you accept in the app.

HR and people data mapping — AdaptivMapr — AdaptivMapr