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.

CSV master-data import

Exact CSV format for importing people, clients and placements into TimeTracking: header aliases (Dutch and English), date formats, matching rules, dry run and the POST /import response.

Last updated: · Markdown version

You can import people, clients and placements from one CSV file. Use it for onboarding and for systems that only export spreadsheets. For ongoing system-to-system syncs, prefer PUT /people|/clients|/placements/by-external/{source}/{ref} and the batch endpoints (see the API reference).

Ways to run it:

  • Dashboard: "Importeren" (upload, preview, then apply).
  • API: POST /import {csv, dryRun} (write key).
  • CLI: tt import FILE.csv (dry run), then tt import FILE.csv --commit.

Format

  • One header row, then one row per person (with an optional placement).
  • The delimiter is detected automatically. ; (Excel NL default), , and tab all work.
  • Encoding: UTF-8. A BOM is allowed.
  • Header names are matched case-insensitively against the aliases below. Unknown headers are ignored and reported in unmappedHeaders.
Field Accepted headers Required Notes
Employee number employee_number, personeelsnummer, werknemersnummer, nummer yes Matching key for people
First name first_name, voornaam yes
Last name last_name, achternaam yes
Email email, e-mail, emailadres no Needed later for app access
Phone phone, telefoon, mobiel no
Client client, opdrachtgever, klant, inlener no Present → a placement is created or updated
Placement ref placement_ref, plaatsing, plaatsingsnummer, placement no Matching key for placements
Role role, functie no role_title
Start date start_date, startdatum, begindatum when a client is given DD-MM-YYYY, D-M-YYYY, DD/MM/YYYY, DD.MM.YYYY or YYYY-MM-DD
End date end_date, einddatum no Same formats; must not be before the start date

Template (the same as the dashboard download):

personeelsnummer;voornaam;achternaam;email;telefoon;opdrachtgever;plaatsing;functie;startdatum;einddatum
1005;Ahmed;Bakker;ahmed@example.com;+31612345678;VDL Montage;PL-2026-105;Monteur;01-11-2026;

Matching rules

Entity Matched on When found When not found
Person employee_number within the org first name, last name, email and phone are overwritten (empty cells become empty) created
Client name, case-insensitive exact match reused created with that name (approval mode office)
Placement ref within the org (only when a ref is given) person, client, role and dates are overwritten created. Rows without a ref always create a new placement, so re-importing them duplicates placements

Notes:

  • The import does not set external_source/external_ref. Renaming a client in the CSV creates a second client.
  • A person's active flag is not changed by the import.

Dry run (default)

dryRun defaults to true on the API. Nothing is written, and the response shows what would happen.

curl -s -X POST "$TT/import" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  --data-binary @- <<'JSON'
{"dryRun": true, "csv": "personeelsnummer;voornaam;achternaam;email;telefoon;opdrachtgever;plaatsing;functie;startdatum;einddatum\r\n9901;Docs;Voorbeeld;docs@example.com;+31612345678;VDL Montage;PL-DOCS-1;Monteur;01-11-2026;\r\n;Zonder;Nummer;;;;;;;\r\n"}
JSON

Real response:

{
  "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": []
  }
}

row is the 1-based line number in the file (the header is row 1). Row messages are in Dutch:

Message Meaning
personeelsnummer ontbreekt Employee number missing
naam ontbreekt First or last name missing
startdatum ontbreekt of is ongeldig (gebruik DD-MM-JJJJ) A client is given but the start date is missing or unreadable
einddatum ongeldig End date unreadable
einddatum ligt vóór startdatum End date before start date

To apply, send the same request with "dryRun": false. Valid rows are written and invalid rows are skipped and reported. There is no transaction across rows.

Limits

  • csv is at most 5,000,000 characters per request.
  • Rows are processed one by one. Large files (thousands of rows) take a while; split them, or use the batch endpoints.
  • No XLSX upload yet. Save as CSV first.

Building a CSV in code

import csv, io, json, requests
rows = [{"personeelsnummer": "1006", "voornaam": "Lotte", "achternaam": "Peeters", "email": "lotte@example.com",
         "opdrachtgever": "VDL Montage", "plaatsing": "PL-2026-106", "functie": "Monteur", "startdatum": "2026-11-02"}]
buf = io.StringIO()
w = csv.DictWriter(buf, fieldnames=list(rows[0]), delimiter=";")
w.writeheader(); w.writerows(rows)
r = requests.post(f"{TT}/import", headers={"Authorization": f"Bearer {KEY}"}, json={"csv": buf.getvalue(), "dryRun": True})
print(json.dumps(r.json()["data"], indent=2))

See also the office help page Import.