# Carerix integration

> Sync Carerix employees, companies and placements into TimeTracking with external-ID upserts and batches, and report approved hours back with GET /days.

URL: https://timetracking.tech/developers/integrations/ats-crm/carerix
Last updated: 2026-10-24

## 1. Summary

**Status:** Recipe (no native connector).

**Direction:** Carerix to TimeTracking for master data. Optional: TimeTracking to Carerix for approved hours.

Carerix is a staffing ATS/CRM widely used by Dutch agencies. The recipe upserts placed employees, client companies and placements into TimeTracking by their Carerix ids, and optionally writes approved hours back.

## 2. Mechanism

- **Carerix side.** Read through the Carerix API (REST/XML "CxAPI" on older instances, REST/GraphQL on newer). Typical entities: `CREmployee`, `CRCompany`, `CRVacancy`, `CRMatch` / placement (`CRJob` in some setups). Detect changes by polling on modification date, or with Carerix notifications if your contract includes them.
- **TimeTracking side.** `PUT /{clients|people|placements}/by-external/carerix/{id}` or `POST /{clients|people|placements}/batch` with `externalSource: "carerix"`.
- **Hours back.** `GET /days?from&to&status=approved&updated_since=…` (cursor-paginated), triggered by `timesheet.approved` or a schedule.

Assumption — verify with your Carerix admin/consultant: entity names and API flavour differ per Carerix version and licence.

## 3. Prerequisites

- Carerix API credentials with read access to employees, companies and placements.
- TimeTracking API key with scope `write`.
- For webhooks: an admin user or an `admin`-scope API key.
- Storage for the back-sync checkpoint (`updated_since`).

## 4. Entity mapping

| Carerix object.field | TimeTracking field | Notes |
|---|---|---|
| `CREmployee` id | person external ref (`/people/by-external/carerix/{id}`) | |
| `CREmployee` payroll number | `employeeNumber` | Must equal the payroll number used in exports. |
| `CREmployee` first / last name | `firstName` / `lastName` | Put Dutch prefixes in `lastName` ("de Vries"). |
| `CREmployee` email, mobile, language | `email`, `phone`, `locale` | `locale` drives the app language (`nl`, `pl`, `en`, …). |
| `CRCompany` id | client external ref (`/clients/by-external/carerix/{id}`) | |
| `CRCompany` name | `name` | |
| Contact who signs timesheets | client contact | Dashboard only; no API for contacts. |
| Placement / `CRMatch` id | placement external ref; also `ref` | |
| Employee id / company id of the placement | `personExternalRef` / `clientExternalRef` | Same source `carerix`. |
| Placement start / end | `startsOn` / `endsOn` | `YYYY-MM-DD`; omit `endsOn` for open-ended. |
| Function title | `roleTitle` | |
| Placement ended | `DELETE /placements/{id}` (deactivate) or `active: false` | |
| TimeTracking day (`GET /days` item) | Carerix hours/timesheet record | Assumption: depends on the Carerix timesheet module. Key on the day `id`. |

**External-ID strategy.** `source` = `carerix`, `ref` = Carerix id. Upserts are idempotent and **full replaces** (omitted optional fields become `null`). Responses include `created: true|false`.

## 5. Step-by-step setup

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

curl -s -X PUT "$TT/clients/by-external/carerix/2201" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"name":"VDL Montage","externalRef":"2201","approvalMode":"both","mailDomains":"vdl-montage.example"}'

curl -s -X PUT "$TT/people/by-external/carerix/50017" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"employeeNumber":"1001","firstName":"Jan","lastName":"de Vries","email":"jan@example.com","locale":"nl"}'

curl -s -X PUT "$TT/placements/by-external/carerix/90311" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"personExternalRef":"50017","clientExternalRef":"2201","ref":"CX-90311","roleTitle":"Monteur","startsOn":"2026-09-01"}'
```

Initial load: use `POST /clients/batch`, `/people/batch`, `/placements/batch` (≤500 rows each, rows `{externalSource:"carerix", externalRef, …}`), in that order. Check `results[]` for `ok: false` rows. The script structure is the same as the [Bullhorn sync](https://timetracking.tech/developers/integrations/ats-crm/bullhorn#54-sync-script-typescript-node-20).

Hours back:

```sh
curl -s "$TT/days?from=2026-11-01&to=2026-11-30&status=approved,exported&updated_since=2026-11-20T00:00:00Z&limit=500" \
  -H "Authorization: Bearer $KEY"
```

## 6. Sync schedule and triggering events

| Trigger | Action |
|---|---|
| Poll Carerix every 15 min on modification date | Upsert changed employees, companies, placements. |
| Nightly | Re-upsert everything active (idempotent). |
| `timesheet.approved` webhook or schedule | `GET /days?updated_since=…`, write to Carerix. |
| `placement.updated` `{active:false}` | Optional: reflect office deactivations. |

## 7. Corrections, conflicts and locked periods

- TimeTracking owns hours. `conflict.created` tells you a conflict exists; resolve it in TimeTracking.
- A correction moves an approved day back to `submitted` and bumps its `version`; overwrite by day `id` in Carerix.
- Exported/locked days return `423 locked`. Corrections are new entries in an open period.
- Shortening a placement before existing hours returns `409`.

## 8. Testing checklist

- [ ] First upsert returns `created: true`, the repeat `created: false`.
- [ ] Batch with one bad row still writes the rest.
- [ ] Ending a placement deactivates it.
- [ ] `GET /days?updated_since=` returns only changed days.

## 9. Troubleshooting

| Error | Fix |
|---|---|
| `401` invalid key | New key from a current admin. |
| `403` "API key is read-only" | Use a `write` key. |
| `422` "Person and client must exist (by id or external ref) before the placement" | Upsert person and client first, same source. |
| `422` with `issues` | Check `issues[].path`; dates `YYYY-MM-DD`. |
| `409` "N registration(s) exist after …" | Do not shorten before existing hours. |
| `429` | Wait `Retry-After` (60 s). |

## 10. Limits and known gaps

- No API for client contacts.
- The master-data CSV import (`POST /import`) still matches clients by name; prefer the external-ID API for ongoing sync.

## 11. Build it with Claude

```text
Build a Carerix → TimeTracking sync (TypeScript, Node 20). Ask me for the Carerix API flavour (XML CxAPI or
REST/GraphQL) and sample responses for an employee, a company and a placement before writing the mapping.
TimeTracking: https://app.timetracking.tech/api/v1, Bearer $TT_API_KEY, responses { data } / { error }.
Use PUT /clients|/people|/placements/by-external/carerix/{id} (full replace), placements with personExternalRef/clientExternalRef, POST /…/batch for bulk (≤500, per-row
results), DELETE /placements/{id} to deactivate, Idempotency-Key on writes, 429 handling. Back-sync approved
hours with GET /days?from&to&status=approved&updated_since&limit=500 following next_cursor.
Dry-run mode and tests with mocked HTTP. Never delete data.
```
