Use case · HR & People
Import a payroll run with every IBAN checked mod-97.
Payroll files carry salary and bank details in one row — the highest-sensitivity data most companies move. Map them to payroll_v1, have every IBAN checked by its own checksum, and get the review flag back on every run.
The job
What you are actually trying to get done
The payroll bureau exports a file every month and someone re-types it, or writes a one-off script that breaks when a column moves. The data is as sensitive as anything the company holds — salary next to a bank account, per employee. The job is to import it without hand-editing, catch a mistyped IBAN before money moves, and have a record of who approved the mapping.
Where it lands
Payroll
payroll_v1 · risk: high
The iban validator is mod-97 checksum-strict and country-scoped — this template accepts CH, LI, DE, FR, IT and ES. A transposed digit fails the checksum and is reported with its row index, which is the whole point of validating before a payment file is produced.
Canonical columns
- employee_id
- gross_salary
- currency
- pay_period
- iban
- tax_code
6 canonical columns · 3 required · 1 validated
Step by step
The whole run, one call at a time
- 1
Upload the run
Excel is read natively and multi-sheet bylib/xlsx.ts— no SheetJS, no egress. The response lists every worksheet, so a workbook with a summary tab in front of the data is one re-POST away.POST /v1/uploads - 2
Map the German headers
Personalnummer,Bruttolohn,LohnperiodeandSteuercodeare all shipped hints on this template.Kontois a hint oniban. Nothing here reaches a model.POST /v1/uploads/:id/match - 3
Validate the account numbers
Theibanvalidator runs mod-97 over each value and rejects a country the template did not list. A failing row is named inerrorswith its field — before anything is committed.POST /v1/uploads/:id/validate - 4
Gate on the flag
payroll_v1is high-risk, so the mappings response always carrieshitl_status: "pending_review". Your importer reads that and holds the batch for a human — the flag is advisory, so the gate is yours to enforce.requires_hitl: true - 5
Deliver where it needs to go
Inline rows, an HMAC-signed webhook, a serialized file (csv,xlsx,parquet,sql…), or a direct write into a destination connector — combinable in one commit.POST /v1/uploads/:id/commit
In code
Validate before money moves
The validate call is the one that matters here. It runs the template validators over every row and returns errors with their row index and field, capped at 500 so a bad file cannot return an unbounded response.
curl -X POST https://api.adaptivmapr.com/v1/uploads/upl_3d91ab/validate \
-H "Authorization: Bearer $ADAPTIVMAPR_API_KEY"{
"upload_id": "upl_3d91ab",
"template_id": "payroll_v1",
"row_count": 412,
"error_count": 2,
"errors": [
{ "row": 87, "field": "iban", "code": "iban", "message": "checksum failed (mod-97)" },
{ "row": 204, "field": "gross_salary", "code": "number", "message": "not a number: \"n/a\"" }
],
"warnings": []
}- The
ibanvalidator is mod-97 and country-scoped;bicis a separate type. Nineteen validator types ship, and the checksum-strict ones (iban,gtin,npi,gln) genuinely compute the check digit rather than pattern-matching it. - Salary and bank data is PII, not PHI. This job runs on
standardrouting by default and pays no surcharge — PHI routing is available if your data class calls for it, at +20% and behind the BAA. - Every commit writes an audit event to Supabase
audit_logsscoped to yourtenant_id.GET /v1/auditreads it back andPOST /v1/me/audit/exportproduces a signed download token. - Post-response work — wallet debits, usage reporting, audit writes — is registered with the Worker’s
ctx.waitUntilthroughafterResponse(), so it completes after the response is returned rather than being dropped when the isolate stops.
Where the work lands
Which layer resolves this file
| Layer | What it does here | Auto-accepts at | Cost |
|---|---|---|---|
| 1 · statistics | The bureau’s exact header row, once confirmed — every month after the first | ≥100 @ 95% · ≥20 @ 100% | Free · deterministic |
| 2 · heuristic | “Personalnummer”, “Bruttolohn”, “Lohnperiode”, “Steuercode”, “Konto” — all shipped hints | ≥ 0.85 | Free · deterministic |
| 3 · fuzzy | “Währung” → currency after accents and punctuation are normalized away | ≥ 0.80 | Free · pure compute |
| 4 · semantic | A cost-centre or contract label with no shared vocabulary | ≥ 0.78 | Cheap, cached · off without an embedding key |
| 5 · ai | One batched call over anything left — usually nothing on a payroll export | Model pick, constrained to the unclaimed column set | Metered · the only paid layer |
MAPR_EMBEDDING_API_KEY, MAPR_LLM_API_KEY) and fail soft to OFF, so an unconfigured deployment still maps on the deterministic layers rather than erroring.Before you commit
Review, cost and where it routes
It comes back flagged for review
payroll_v1 is a high-risk template, so PATCH /v1/uploads/:id/mappings returns requires_hitl: true and hitl_status: "pending_review" on every call, for every workspace, with nothing to configure.
Honest limitThe flag is derived from the template’s risk field and is advisory in v1 — we set it, your workflow owns the queue. The upload is not server-side blocked. A native AgentGate approval queue is roadmap, not v1.
Usually just the flat fee
When every column resolves on layers 1–3 — the common case for a file you receive regularly — no model runs, so there are no AI tokens to bill. A small flat per-map fee ($0.0010) is drawn on every map — a fully deterministic one and a layout-cache re-map included. There is no free tier and no seats or contract; you top up a prepaid wallet from $10 and it draws down.
Rate cardAI tokens are billed only when a model actually ran, at provider cost ×2 (×0.5 with your own LLM key). A PHI-routed run multiplies the whole charge — flat fee included — by 1.2. See lib/pricing.ts and GET /v1/pricing.
Standard routing, region still pinned
This job carries no protected health information, so it runs on standard routing with no surcharge. The workspace region pin still applies — the routing class picks the model catalogue; the region decides where compute may run.
MechanismresolveRunPhi() in lib/agreements.ts resolves the axis per run from phi_mode in the body, falling back to tenants.phi_mode (default false). PHI is available on this job too if your data class calls for it — it costs +20% and needs the BAA.
Questions
The things people actually ask
Does salary data get sent to an AI model?
What exactly does the IBAN validator check?
Can we block the commit until a person signs off?
What does one payroll import cost?
Stop hand-mapping this file. Map it once.
Start with a $10 prepaid wallet. In schema-only mode only headers and up to three sample rows — each cell clamped to 80 characters — ever leave you.