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.
Solution · HR & people
Salaries and national IDs in a spreadsheet, going from one vendor to another. The mapping happens without a human reading the rows.
HR & People
The job
What changes
What actually arrives
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.
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.
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.
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.
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.
HR & People — every field carries DE / FR / IT / EN / ES hints.
1 medium + 2 high — PATCH /mappings returns requires_hitl: true.
iban, email, phone, regex
How it works
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.
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.
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.
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.
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.
Field level
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| Field | Type | Required | Validators | Header vocabulary it already knows |
|---|---|---|---|---|
employee_id | string | required | — | personalnummer · matricule · matricola · employee id +1 |
legal_name | string | required | — | name · nom · nome · legal name +1 |
email | — | email · e-mail · mail · correo +1 | ||
national_id | string | — | regex | ausweisnummer · ahv · numéro national · codice fiscale +2 |
iban | string | — | iban | iban · compte · konto · bank account |
hire_date | date | — | — | eintrittsdatum · date d'embauche · data di assunzione · hire date +1 |
department | string | — | — | abteilung · département · dipartimento · department +1 |
status | enum (3) | — | — | status · statut · stato · estado |
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
What the hr & people pack gives you that a generic importer does not — and, under each card, the mechanism that does it.
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.
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.
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.
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
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.
Employee records are personal data processed under an employment relationship, and Art. 5(1)(c) still applies: process the minimum necessary for the purpose.
A processor has to be able to destroy what it holds, completely, and show that it did.
Salary data from one workspace must not be able to influence — or be visible to — another.
An HR or works-council review asks who mapped what, when, and to which field.
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
Every template this vertical ships, with the risk level that decides whether a commit gates on human review.
| Template id | Risk | Commit gate | Validators |
|---|---|---|---|
employees_v1 | high | requires_hitl: true | email, regex, iban |
payroll_v1 | high | requires_hitl: true | iban |
time_off_v1 | low | requires_hitl: false | — |
candidates_v1 | medium | requires_hitl: true | email, phone |
GET /v1/templates returns. Nothing on this table is typed by hand.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.
In this vertical, out of 4 templates.
Across all 7 packs the engine serves on GET /v1/templates.
[
{ "source_col": "Personalnummer",
"target_field": "employee_id",
"user_confirmed": true }
]{
"upload_id": "upl_7a41c2…",
"mappings": [ … ],
"requires_hitl": true,
"hitl_status": "pending_review"
}In code
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.
/v1/uploadsparse the file, return its headers + ≤3 clamped sample rows/v1/uploads/:id/matchrun the six-layer cascade against one template/v1/uploads/:id/mappingsrecord confirmations; returns requires_hitl/v1/uploads/:id/validaterun the field validators over every row, indexed/v1/uploads/:id/commitemit and deliver — inline, webhook or DB connectorAuthentication 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.
# 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}]'{
"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"
}Rollout
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.
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”.
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.
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.
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.
FAQ
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.