# Visma | Raet (Youforce) payroll export

> Deliver approved TimeTracking hours, allowances, expenses and leave to Visma | Raet Youforce payroll as a variable-data file.

URL: https://timetracking.tech/developers/integrations/payroll/visma-raet
Last updated: 2026-10-24

## 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 `write` scope.
- `employee_number` in TimeTracking = Youforce personnel number (often with leading zeros: use `pad`).
- 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

```sh
export TT=https://app.timetracking.tech/api/v1
export KEY=tt_live_…        # write scope
```

1. Create the mapping yourself (admin, dashboard): open the export mappings screen (`/export/mappings`, linked from **Export**), copy `preset-generic` or `preset-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 as `mappingId` to `POST /exports` and `tt export --mapping`. Example JSON for the editor (assumed layout — verify with your Visma | Raet consultant):

```json
{
  "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.

2. Preview and commit:

```sh
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
```

3. If Youforce needs fixed width, convert after commit. Example (field widths are assumptions):

```sh
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
```

4. 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.completed` and `period.locked` webhooks (admin creates them). On `export.completed`, download the file with `GET /exports/runs/{runId}/file`, post-process and deliver. See [Webhook events](https://timetracking.tech/developers/webhooks/events).
- Committed files stay retrievable: `GET /exports/runs` (paginated `{items, next_cursor}`), `GET /exports/runs/{id}` (metadata, `mapping_snapshot`, `pay_lines`, `warnings`) and `GET /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 with `GET /exports/runs/{id}/file` (or convert the UTF-8 copy with `iconv -f UTF-8 -t WINDOWS-1252`).
- `groupBy` keeps the first line's date.

- Encoding: a mapping with `encoding: "windows-1252"` is delivered as Windows-1252 (Latin-1) only by `GET /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 JSON `content` and the dashboard download stay UTF-8.

> **Planned (not in API v1.1)** — export-mapping CRUD via the API.

## 11. Build it with Claude

```text
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>
```
