Skip to content

Request an integration

Tell us which system you want to connect. We review every request weekly and publish a step-by-step guide or build the connection.

Easyflex payroll and placements

Sync Easyflex flex workers and placements into TimeTracking and send approved hours back to Easyflex per placement, using CSV import, the REST API and an hours file.

Last updated: · Markdown version

1. Summary

What syncs In: flex workers, clients (inleners), placements. Out: approved hours per placement per hour type, expenses, leave
Direction Easyflex → TimeTracking (master data), TimeTracking → Easyflex (hours)
Mechanism Master data: CSV import (POST /import) or REST upserts. Hours: CSV export with a custom mapping imported as an Easyflex hours file, or a script against Easyflex web services
Trigger Master data daily or on change; hours per payroll week

Status: Recipe — works today with files and your own script. No native Easyflex connector.

2. Mechanism

Easyflex is a staffing back office: it owns flex workers, placements (plaatsingen) and payroll per placement. TimeTracking needs the same placements so hours are recorded per placement.

  1. Master data in. Export flex workers and placements from Easyflex to CSV and import them with CSV import (personeelsnummer, opdrachtgever, plaatsing, startdatum …). People are matched on employee_number, placements on ref, clients on name. For a scripted sync, use the by-external upserts and batch endpoints instead (step 2b).
  2. Hours out. After approval, a custom export mapping writes one line per placement per day (or per week) per hour type with the Easyflex placement number. You import it in Easyflex as an hours file.

Assumption — Easyflex accepts hours imports per placement (plaatsingnummer) with an hour code or wage component (uurcode / looncomponent) and supports SOAP/XML web services for flex workers, placements and hours. File layout, web service names and authentication must be verified with your Easyflex consultant.

3. Prerequisites

  • TimeTracking admin; API key with write scope.
  • Easyflex flex worker number (registratienummer) stored as TimeTracking employee_number.
  • Easyflex placement number stored as TimeTracking placement ref.
  • Easyflex hour codes per TimeTracking hour type.
  • Rule pack aligned with the CAO (ABU/NBBU or inlenersbeloning). Assumption — some agencies let Easyflex calculate surcharges itself; then export only worked hours per day with times and map every hour type to the base code (see section 10).

4. Entity mapping

Easyflex object / field TimeTracking Notes / external ID
Flexkracht registratienummer people.employee_number CSV header must be personeelsnummer, werknemersnummer, nummer or employee_number; registratienummer is not an accepted alias, so rename it. Only stable key today.
Voornaam / achternaam / e-mail / mobiel first_name, last_name, email, phone CSV aliases voornaam, achternaam, email, telefoon/mobiel.
Relatie / inlener clients.name Import matches clients by name (case-insensitive). external_ref can be set via POST /clients.
Plaatsing (nummer) placements.ref CSV plaatsing / plaatsingsnummer. Used to update existing placements.
Functie placements.role_title CSV functie.
Begindatum / einddatum starts_on / ends_on DD-MM-JJJJ or YYYY-MM-DD.
Uurcode / looncomponent PayLine hourType via map
Uren minutes → hours_decimal
Datum PayLine date Do not groupBy if Easyflex needs per-day lines.
Declaraties expense_<kind> lines, amountCents

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. Use source easyflex with the registratienummer, relatie number and plaatsingnummer as refs. Easyflex stays the source of truth.

5. Step-by-step setup

export TT=https://app.timetracking.tech/api/v1
export KEY=tt_live_…        # write scope
  1. Export placements from Easyflex to CSV and rename headers to TimeTracking aliases:
personeelsnummer;voornaam;achternaam;email;telefoon;opdrachtgever;plaatsing;functie;startdatum;einddatum
104577;Piotr;Nowak;piotr@example.com;+48500100200;Logistiek Parkstad;PL-2026-103;Heftruckchauffeur;01-10-2026;
  1. Dry run, then import:
tt import easyflex-plaatsingen.csv            # dry run
tt import easyflex-plaatsingen.csv --commit

Real dry-run response shape:

{ "data": { "dryRun": true,
  "rows": [ { "row": 2, "ok": true, "person": "Docs Voorbeeld", "client": "VDL Montage" },
            { "row": 3, "ok": false, "message": "personeelsnummer ontbreekt" } ],
  "people": { "created": 1, "updated": 0 }, "clients": { "created": 0 },
  "placements": { "created": 1, "updated": 0 }, "unmappedHeaders": [] } }

2b. For a scripted daily sync, upsert by Easyflex ids instead of the CSV import (idempotent, safe to rerun):

# client
curl -s -X PUT "$TT/clients/by-external/easyflex/REL-3301" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"name":"Logistiek Parkstad","externalRef":"REL-3301","approvalMode":"client"}'
# flex worker
curl -s -X PUT "$TT/people/by-external/easyflex/104577" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"employeeNumber":"104577","firstName":"Piotr","lastName":"Nowak","email":"piotr@example.com","locale":"pl"}'
# placement, linked by the external refs of the same source
curl -s -X PUT "$TT/placements/by-external/easyflex/PL-2026-103" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"personExternalRef":"104577","clientExternalRef":"REL-3301","ref":"PL-2026-103","roleTitle":"Heftruckchauffeur","startsOn":"2026-10-01"}'

Real response shape (placement upsert, demo data):

{ "data": { "id": "96574dce-db96-4da1-82cf-de32021472d9", "person_id": "69a47995-bc6a-4bc9-9b9b-070935b974bb",
  "client_id": "a3254709-d152-49b4-ad5e-51d50147910a", "ref": "PL-555", "role_title": "Lasser", "starts_on": "2026-12-01",
  "ends_on": null, "active": true, "external_source": "bullhorn", "external_ref": "PL-555", "created": true } }

For volume, send up to 500 rows per call to POST /people/batch or POST /placements/batch with externalSource and externalRef on each row; the response lists {index, ok, data | error} per row and one bad row never aborts the batch. Ended placements: DELETE /placements/{id} deactivates (active: false). Send an Idempotency-Key header on writes so a network retry replays the stored response (idempotent-replayed: true).

  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 Easyflex consultant):
{
  "id": "easyflex-uren",
  "name": "Easyflex — urenbestand per plaatsing",
  "format": "csv",
  "delimiter": ";",
  "includeHeader": true,
  "encoding": "utf-8",
  "lineEnding": "crlf",
  "columns": [
    { "header": "Registratienummer", "source": "employeeNumber" },
    { "header": "Plaatsingnummer", "source": "placementRef" },
    { "header": "Datum", "source": "date", "transform": { "kind": "date", "format": "DD-MM-YYYY" } },
    { "header": "Uurcode", "source": "hourType", "transform": { "kind": "map", "table": "uurcode" } },
    { "header": "Uren", "source": "minutes", "transform": { "kind": "hours_decimal", "decimals": 2, "decimalSeparator": "," } },
    { "header": "Bedrag", "source": "amountCents", "transform": { "kind": "cents_decimal", "decimalSeparator": "," } }
  ],
  "tables": { "uurcode": { "normal": "100", "overtime_125": "125", "overtime_150": "150", "ort_evening": "201",
                           "ort_night": "202", "ort_saturday": "203", "ort_sunday": "204", "holiday": "205",
                           "expense_km": "KM", "expense_public_transport": "OV" } },
  "validation": [
    { "column": "Registratienummer", "required": true },
    { "column": "Uurcode", "required": true },
    { "column": "Uren", "min": 0, "max": 24 }
  ]
}

Leave lines have no placement (placementRef empty). If Easyflex needs a placement for leave, keep leave in Easyflex and leave leave_* unmapped — then remove Uurcode required or add a fallback.

  1. Weekly: preview, commit, import:
tt export --week 2026-11-30 --mapping easyflex-uren
tt export --week 2026-11-30 --mapping easyflex-uren --commit -o easyflex-w49.csv
  1. After Easyflex processed the week, an admin locks it in the dashboard.

6. Sync schedule and triggering events

Flow Schedule Event
Placements Easyflex → TimeTracking Daily, or when a placement starts/ends Upserts emit person.created/person.updated and placement.created/placement.updated
Hours TimeTracking → Easyflex Weekly after close export.completed → download with GET /exports/runs/{runId}/file
Period final After Easyflex payroll period.locked

7. Corrections, conflicts and locked periods

  • Shortening a placement fails with 409 "N registration(s) exist after " when hours exist after the new end date.
  • Only approved, conflict-free days are exported; exported/locked days return 423 on new claims.
  • Corrections for an exported week: record them in the current open week; they reach Easyflex in the next file with their own date.
  • Reconcile before export: GET /days?from=2026-11-30&to=2026-12-06&status=submitted,approved&client_id=<id> (read key) lists day values per placement with status and conflict flag.

8. Testing checklist

  • Dry-run import has no failed rows and unmappedHeaders: [].
  • Every active Easyflex placement exists in TimeTracking with the same ref.
  • Hours file: every line has Plaatsingnummer; codes mapped.
  • Test import in Easyflex; compare per-placement totals.

9. Troubleshooting

HTTP / code Message Fix
200 import row ok:false personeelsnummer ontbreekt / naam ontbreekt / startdatum ontbreekt of is ongeldig (gebruik DD-MM-JJJJ) Fix the CSV row.
409 conflict N registration(s) exist after Hours exist after the new end date; correct hours first.
403 forbidden API key is read-only write key.
404 not_found Export mapping not found Check mapping id (UUID from /export/mappings/<uuid>).
422 invalid Person and client must exist (by id or external ref) before the placement Upsert person and client (same source) first.
422 invalid Export has N validation issue(s) Preview and fix.
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

  • Clients are matched by name in the CSV import; renaming a client in Easyflex creates a new client. The by-external upserts avoid this.

  • Neither import nor upserts deactivate missing records; call DELETE /people/{id} / DELETE /placements/{id} yourself.

  • Mappings are edited by admins in the dashboard only; there is no mapping API.

  • If Easyflex calculates surcharges itself, use the plain rule pack so TimeTracking exports only normal hours.

  • 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

Build an Easyflex ↔ TimeTracking sync script.
1. Preferred: upsert clients, people and placements with PUT /api/v1/{clients|people|placements}/by-external/easyflex/{ref}
   (or the /batch endpoints, 500 rows max). Fallback: read an Easyflex placements CSV export, rename columns to the TimeTracking import aliases
   (personeelsnummer, voornaam, achternaam, email, telefoon, opdrachtgever, plaatsing, functie, startdatum, einddatum),
   and call POST /api/v1/import with dryRun true; show failed rows. Only import with --commit.
2. Weekly: POST /api/v1/exports with mappingId "easyflex-uren", preview then (only with --commit) commit, and save the CSV.
3. Report placements that exist in Easyflex but not in TimeTracking (match on placement ref) and vice versa.
Mark any Easyflex web-service call as TODO (verify with Easyflex consultant). Use TT_API_KEY from the environment.