OTYS integration
Sync OTYS candidates, relations and placements into TimeTracking with external-ID upserts, batches and the OTYS web services.
Last updated: · Markdown version
1. Summary
Status: Recipe (no native connector).
Direction: OTYS to TimeTracking for master data. Optional: approved hours back.
2. Mechanism
- OTYS side. OTYS JSON-RPC web services (OWS) for candidates, relations (companies), vacancies and procedures/placements. Poll on modification date.
- TimeTracking side.
PUT /{clients|people|placements}/by-external/otys/{uid}or the batch endpoints withexternalSource: "otys". - Hours back (optional).
GET /days?status=approved&updated_since=….
Assumption — verify with your OTYS consultant: service and field names (for example Otys.Services.CandidateService, RelationService, procedure status "placed") vary by module and licence.
3. Prerequisites
- OTYS web services API key with read rights on candidates, relations and procedures.
- TimeTracking API key with scope
write(adminif the job also manages webhooks).
4. Entity mapping
| OTYS object.field | TimeTracking field | Notes |
|---|---|---|
| Candidate uid | person external ref (/people/by-external/otys/{uid}) |
|
| Candidate payroll number | employeeNumber |
|
| First name / last name incl. prefix | firstName / lastName |
Prefix in lastName. |
| Email, mobile, language | email, phone, locale |
|
| Relation uid | client external ref | |
| Relation name | name |
|
| Procedure/placement uid | placement external ref; ref |
|
| Candidate uid / relation uid | personExternalRef / clientExternalRef |
|
| Vacancy title | roleTitle |
|
| Start / end | startsOn / endsOn |
|
| Procedure closed | DELETE /placements/{id} (deactivate) |
External-ID strategy. source = otys, ref = OTYS uid. Idempotent upserts, full replace semantics, created: true|false in responses.
5. Step-by-step setup
export TT=https://app.timetracking.tech/api/v1
export KEY=tt_live_...
curl -s -X PUT "$TT/clients/by-external/otys/5521" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{"name":"Bakkerij Ruijters","externalRef":"5521"}'
curl -s -X PUT "$TT/people/by-external/otys/c-88412" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{"employeeNumber":"1004","firstName":"Sanne","lastName":"Janssen"}'
curl -s -X PUT "$TT/placements/by-external/otys/77810" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{"personExternalRef":"c-88412","clientExternalRef":"5521","ref":"OT-77810","roleTitle":"Productiemedewerker","startsOn":"2026-10-01"}'
For bulk loads use POST /clients/batch, /people/batch, /placements/batch (≤500 rows). Structure the job like the Bullhorn sync script.
6. Sync schedule and triggering events
| Trigger | Action |
|---|---|
| Poll OTYS every 15 min | Upsert changed records. |
| Nightly | Re-upsert all active records. |
timesheet.approved or schedule (optional) |
GET /days?updated_since=… and write back. |
7. Corrections, conflicts and locked periods
TimeTracking owns hours, conflicts and locks. Corrections bump the day version and return it to submitted. Exported/locked days return 423. Ending a placement before existing hours returns 409.
8. Testing checklist
- Re-runs return
created: falseand create no duplicates. - Name prefix lands in
lastName. - Closed procedure deactivates the placement.
9. Troubleshooting
| Error | Fix |
|---|---|
401 |
New key from a current admin. |
403 read-only |
Use a write key. |
422 "Person and client must exist …" |
Upsert person and client first. |
409 |
Placement shortened before hours. |
429 |
Wait Retry-After. |
10. Limits and known gaps
No client-contact API. Native OTYS app: Planned.
11. Build it with Claude
Build an OTYS → TimeTracking placement sync (TypeScript). Ask me for the OTYS web-service names and a sample
candidate, relation and placement response first. TimeTracking: https://app.timetracking.tech/api/v1, Bearer
$TT_API_KEY. Use PUT /clients|/people|/placements/by-external/otys/{uid} (full replace; placements use personExternalRef/clientExternalRef), POST /…/batch for bulk,
DELETE /placements/{id} to deactivate, Idempotency-Key on writes, 429 handling, dry-run mode, mocked-HTTP tests.