# OTYS integration

> Sync OTYS candidates, relations and placements into TimeTracking with external-ID upserts, batches and the OTYS web services.

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

## 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 with `externalSource: "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` (`admin` if 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

```sh
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](https://timetracking.tech/developers/integrations/ats-crm/bullhorn#54-sync-script-typescript-node-20).

## 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: false` and 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

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