Visma | Raet (Youforce) payroll export
Deliver approved TimeTracking hours, allowances, expenses and leave to Visma | Raet Youforce payroll as a variable-data file.
Last updated: · Markdown version
1. Summary
| What syncs | Approved hours per hour type, allowances, approved expenses, approved leave |
| Direction | TimeTracking → Visma | Raet Youforce payroll |
| Mechanism | CSV export with a custom mapping, delivered as a variable-data (mutaties) file |
| Trigger | Per payroll period |
Status: Recipe — works today with a file that matches your Youforce variable-data layout, built by an admin as a custom export mapping in the dashboard. No native connector.
2. Mechanism
TimeTracking renders one line per employee per wage component per period with a custom export mapping. The file is uploaded (or delivered by your payroll administrator) into Youforce as variable data.
Assumption — Youforce payroll accepts variable data ("variabele gegevens"/mutaties) through a file interface with a fixed record layout per customer contract (sometimes fixed-width, sometimes delimited, often with employer and period fields). The exact layout, field lengths and code lists are customer-specific; verify with your Visma | Raet consultant.
Fixed-width layouts can be approximated with pad transforms and delimiter |, but TimeTracking cannot emit true fixed-width records without a delimiter. If Youforce requires fixed width, post-process the file (see section 5).
3. Prerequisites
- TimeTracking admin; API key with
writescope. employee_numberin TimeTracking = Youforce personnel number (often with leading zeros: usepad).- Youforce wage component codes (looncomponenten) per hour type, expense kind and leave type.
- Employer number and the record layout from your Visma | Raet consultant.
4. Entity mapping
| Youforce object / field | TimeTracking | Notes / external ID |
|---|---|---|
| Werkgeversnummer | TimeTracking org | const: column. |
| Personeelsnummer | employee_number → employeeNumber |
pad to the required length. Only stable key today. |
| Dienstverbandvolgnummer | Not modelled | const:1 if always the first employment. Assumption — verify. |
| Looncomponent | hourType via map |
|
| Waarde (hours/units) | minutes → hours_decimal |
Some layouts want hundredths without separator: post-process. |
| Bedrag | amountCents |
cents_decimal, or raw cents via plain source (amountCents without transform). |
| Ingangsdatum / periode | date (YYYYMMDD) or constant |
External IDs: upsert people, clients and placements idempotently with PUT /{people|clients|placements}/by-external/{source}/{ref} (or POST /…/batch with externalSource + externalRef per row, up to 500 rows). The response includes external_source, external_ref and created: true|false.
5. Step-by-step setup
export TT=https://app.timetracking.tech/api/v1
export KEY=tt_live_… # write scope
- Create the mapping yourself (admin, dashboard): open the export mappings screen (
/export/mappings, linked from Export), copypreset-genericorpreset-sdworx, edit columns, code tables and validation, and optionally set it as the org default. Saved mappings are validated; max 50 per org. The mapping id is the UUID in its URL (/export/mappings/<uuid>): pass it asmappingIdtoPOST /exportsandtt export --mapping. Example JSON for the editor (assumed layout — verify with your Visma | Raet consultant):
{
"id": "youforce-mutaties",
"name": "Visma | Raet Youforce — variabele gegevens",
"format": "txt",
"delimiter": ";",
"includeHeader": false,
"encoding": "utf-8",
"lineEnding": "crlf",
"groupBy": ["employeeNumber", "hourType"],
"columns": [
{ "header": "Werkgever", "source": "const:000123" },
{ "header": "Personeelsnummer", "source": "employeeNumber", "transform": { "kind": "pad", "length": 8, "char": "0" } },
{ "header": "Dienstverband", "source": "const:1" },
{ "header": "Looncomponent", "source": "hourType", "transform": { "kind": "map", "table": "lc" } },
{ "header": "Waarde", "source": "minutes", "transform": { "kind": "hours_decimal", "decimals": 2, "decimalSeparator": "," } },
{ "header": "Bedrag", "source": "amountCents", "transform": { "kind": "cents_decimal", "decimalSeparator": "," } },
{ "header": "Ingangsdatum", "source": "date", "transform": { "kind": "date", "format": "YYYYMMDD" } }
],
"tables": { "lc": { "normal": "0100", "overtime_125": "0125", "ort_sunday": "0240", "expense_km": "0410", "leave_vacation": "0300" } },
"validation": [
{ "column": "Personeelsnummer", "required": true, "pattern": "^\\d{8}$" },
{ "column": "Looncomponent", "required": true }
]
}
Codes are placeholders.
- Preview and commit:
tt export --from 2026-11-01 --to 2026-11-30 --mapping youforce-mutaties
tt export --from 2026-11-01 --to 2026-11-30 --mapping youforce-mutaties --commit -o youforce-2026-11.txt
- If Youforce needs fixed width, convert after commit. Example (field widths are assumptions):
awk -F';' 'BEGIN{OFS=""} {printf "%-6s%-8s%-2s%-4s%10s\r\n", $1,$2,$3,$4,$5}' youforce-2026-11.txt > youforce-2026-11.fixed
- Deliver the file to Youforce. After processing, an admin locks the period in the dashboard.
6. Sync schedule and triggering events
- Monthly (or per Youforce payroll period).
export.completedandperiod.lockedwebhooks (admin creates them). Onexport.completed, download the file withGET /exports/runs/{runId}/file, post-process and deliver. See Webhook events.- Committed files stay retrievable:
GET /exports/runs(paginated{items, next_cursor}),GET /exports/runs/{id}(metadata,mapping_snapshot,pay_lines,warnings) andGET /exports/runs/{id}/file(the exact committed file,text/csv). A read-only key is enough.
7. Corrections, conflicts and locked periods
- Only approved, conflict-free days are exported.
- Exported/locked days are immutable (
423). Book corrections in an open period. - Youforce rejections are fixed on the Youforce side from the saved file.
8. Testing checklist
- Layout validated by Visma | Raet on a sample file.
- Personnel numbers padded correctly; all codes mapped.
- Test run totals equal TimeTracking totals.
9. Troubleshooting
| HTTP / code | Message | Fix |
|---|---|---|
401 unauthenticated |
Invalid or revoked API key | New key. |
403 forbidden |
API key is read-only | write key. |
404 not_found |
Export mapping not found | Check mapping id. |
409 conflict |
This period was just exported; refresh before exporting again | Avoid double commit. |
422 invalid |
Export has N validation issue(s) | Preview; often unpadded numbers or unmapped codes. |
422 invalid |
Nothing approved to export in this period | Approve first. |
423 locked |
This day is exported or locked; book a correction in an open period | Correct in an open period. |
429 rate_limited |
Rate limit … exceeded | Wait 60 s. |
10. Limits and known gaps
No true fixed-width output; post-process if required.
Mappings are edited by admins in the dashboard only; there is no mapping API.
If Youforce needs ANSI, set
"encoding": "windows-1252"in the mapping and download the run withGET /exports/runs/{id}/file(or convert the UTF-8 copy withiconv -f UTF-8 -t WINDOWS-1252).groupBykeeps the first line's date.Encoding: a mapping with
encoding: "windows-1252"is delivered as Windows-1252 (Latin-1) only byGET /exports/runs/{id}/file(content-type: text/csv; charset=windows-1252, no BOM). Characters outside Latin-1 (€, curly quotes, em dash) become?. The preview/commit JSONcontentand the dashboard download stay UTF-8.
Planned (not in API v1.1) — export-mapping CRUD via the API.
11. Build it with Claude
I have a Visma | Raet Youforce variable-data record layout (pasted below). Using
docs/developers/data-out/export-mappings.md, write a TimeTracking ExportMapping JSON that matches it as closely as
possible, plus an awk or Node post-processor for anything the mapping cannot express (fixed width, ANSI encoding,
header/trailer records). List every assumption that I must verify with our Visma | Raet consultant.
<paste layout here>