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.

Mysolution staffing and payroll

Connect Mysolution (Salesforce-based staffing software) to TimeTracking: placements in via external-ID upserts, approved hours out via GET /days or export runs and webhooks.

Last updated: · Markdown version

1. Summary

What syncs In: candidates/employees, accounts (clients), placements. Out: approved hours per placement per hour type, expenses
Direction Mysolution → TimeTracking (master data), TimeTracking → Mysolution (hours)
Mechanism Your middleware (Salesforce Apex/Flow, iPaaS, or a script) calling the TimeTracking REST API: PUT …/by-external/mysolution/{ref} upserts in, GET /days or export runs out
Trigger Placement changes in Mysolution; approval and export webhooks in TimeTracking

Status: Recipe — works today with your own middleware. No native Mysolution connector.

2. Mechanism

Mysolution runs on Salesforce. Assumption — placements, candidates and accounts are Salesforce (custom) objects in your org, and hours/timesheets can be written through the Salesforce REST API or a Mysolution-provided API; object and field API names differ per installation. Verify with your Mysolution consultant.

  1. Master data in. On placement create/update in Mysolution, middleware upserts the client, person and placement in TimeTracking by their Salesforce ids: PUT /clients/by-external/mysolution/{accountId}, PUT /people/by-external/mysolution/{candidateId}, PUT /placements/by-external/mysolution/{placementId}. These calls are idempotent; no TimeTracking ids need to be stored in Salesforce. Nightly full syncs use POST /…/batch (≤ 500 rows per call).
  2. Hours out. Two options:
    • Approved day values (for timesheets/invoicing in Mysolution): GET /days?from=…&to=…&status=approved,exported (read key) returns per-day minutes, start/end and break per placement. Paginate with limit/cursor; poll incrementally with updated_since.
    • Payroll lines with hour types (overtime, ORT, expenses): commit an export (POST /exports) or react to export.completed and download the committed file with GET /exports/runs/{runId}/file.

3. Prerequisites

  • TimeTracking API key with write scope for upserts; a separate read key is enough for GET /days and export-run downloads.
  • Salesforce integration user and connected app for your middleware.
  • A fixed external source name, e.g. mysolution ([a-z0-9_-]{1,40}), used on every upsert.
  • Mysolution hour codes per TimeTracking hour type (only for the payroll-lines option).

4. Entity mapping

Mysolution (Salesforce) object / field TimeTracking Notes / external ID
Account Id clients via PUT /clients/by-external/mysolution/{AccountId} Repeat calls keep the stored ref.
Account name, approval setting name, approvalMode (office/client/both)
Account contact who approves hours client_contacts (approver) Managed in the dashboard; no API to create contacts.
Candidate Id people via PUT /people/by-external/mysolution/{CandidateId}
Employee number, first/last name, email, mobile employeeNumber, firstName, lastName, email, phone Upsert body is the full object; omitted optional fields become null.
Placement Id placements via PUT /placements/by-external/mysolution/{PlacementId} Link with personExternalRef + clientExternalRef (same source).
Placement number, start/end, function ref, startsOn, endsOn, roleTitle
Placement ended/cancelled DELETE /placements/{id} → active: false Deactivates; hours stay.
Timesheet line (date, hours, start/end) GET /days item: work_date, minutes, start_min, end_min, break_min, status Join placement_id to the placement's external_ref (GET /placements/{id}).
Timesheet line with hour code Export file row: placementRef, date, hourType, minutes Only after commit.

5. Step-by-step setup

export TT=https://app.timetracking.tech/api/v1
export KEY=tt_live_…        # write scope
  1. Upsert the client:
curl -s -X PUT "$TT/clients/by-external/mysolution/001Qy00000AbCdEIAV" \
  -H "Authorization: Bearer $KEY" -H "content-type: application/json" -H "Idempotency-Key: sf-001Qy00000AbCdEIAV-v3" \
  -d '{"name":"Logistiek Parkstad","externalRef":"001Qy00000AbCdEIAV","approvalMode":"client","mailDomains":"logistiekparkstad.example"}'
  1. Upsert the person:
curl -s -X PUT "$TT/people/by-external/mysolution/003Qy00000XyZ12IAB" \
  -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"employeeNumber":"104577","firstName":"Piotr","lastName":"Nowak","email":"piotr@example.com","locale":"pl"}'

Real response (demo data, source bullhorn; abridged):

{ "data": { "id": "69a47995-bc6a-4bc9-9b9b-070935b974bb", "employee_number": "9910", "first_name": "Eva",
  "last_name": "Docs", "email": "eva.docs@example.com", "active": true,
  "external_source": "bullhorn", "external_ref": "12345", "created": true } }

A repeat call returns the same id with "created": false. Person upserts emit person.created / person.updated.

  1. Upsert the placement by external refs:
curl -s -X PUT "$TT/placements/by-external/mysolution/a0X7Q00000PLC103" \
  -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"personExternalRef":"003Qy00000XyZ12IAB","clientExternalRef":"001Qy00000AbCdEIAV","ref":"PLC-103","roleTitle":"Heftruckchauffeur","startsOn":"2026-10-01"}'

Missing person or client → 422 Person and client must exist (by id or external ref) before the placement.

  1. Nightly full sync with batch (≤ 500 rows, sequential, one bad row never aborts):
curl -s -X POST "$TT/people/batch" -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"items":[{"externalSource":"mysolution","externalRef":"003Qy00000XyZ12IAB","employeeNumber":"104577","firstName":"Piotr","lastName":"Nowak"}]}'

Real response shape:

{ "data": { "ok": 1, "failed": 1, "results": [
  { "index": 0, "ok": true, "data": { "id": "02595da9-06ea-45d1-a5e8-927d854a2c46", "external_ref": "12346", "created": true } },
  { "index": 1, "ok": false, "error": { "code": "invalid", "message": "firstName: Too small: expected string to have >=1 characters; lastName: Invalid input: expected string, received undefined" } } ] } }
  1. Hours out — approved day values, incremental:
curl -s "$TT/days?from=2026-11-30&to=2026-12-13&status=approved,exported&updated_since=2026-12-07T00:00:00Z&limit=500" \
  -H "Authorization: Bearer $KEY"

Real item (demo data):

{ "id": "0ce9b17d-40ab-4542-937a-de57896a7358", "placement_id": "00000000-0000-4000-8000-0000000000e4",
  "person_id": "00000000-0000-4000-8000-0000000000d4", "work_date": "2026-12-01", "minutes": 480,
  "start_min": 420, "end_min": 930, "break_min": 30, "status": "exported", "resolution": "office_choice",
  "has_conflict": false, "export_run_id": "239a1898-fab5-486c-a2be-92d939be90cc", "version": 6,
  "updated_at": "2026-10-23T19:08:42.871612+00:00" }

Follow next_cursor until it is null. Write each item to the Mysolution timesheet object; use the day id (and version) to update instead of duplicate.

  1. Hours out — payroll lines: on export.completed {runId, …} download the committed file:
curl -s "$TT/exports/runs/<runId>/file" -H "Authorization: Bearer $KEY" -o run.csv

preset-generic columns: employee_number;employee_name;client;placement;date;hour_type;hours (per day, decimal point, no amounts). For expenses and Mysolution hour codes, an admin builds a custom mapping in the dashboard (/export/mappings).

6. Sync schedule and triggering events

Flow Trigger
Placement upsert Salesforce record-triggered Flow / Apex on placement change; nightly batch as safety net
Day values out timesheet.approved {dayIds, approved} / timesheet.client_approved → GET /days?updated_since=…; or hourly poll
Payroll lines out export.completed → GET /exports/runs/{runId}/file
Echo of upserts (optional) person.created/updated, placement.created/updated

Webhooks are managed by an admin (dashboard, admin user token or an admin-scope API key). Verify tt-signature (Verify signatures); dedupe on the event id, because a replay (POST /webhook-deliveries/{id}/replay) resends the same id.

7. Corrections, conflicts and locked periods

  • Ending a placement early fails with 409 when hours exist after the new end date.
  • A changed value on an approved day moves it back to submitted and bumps version; your updated_since poll picks it up.
  • Exported/locked days are immutable (423); corrections go into an open week and appear as new days/lines.
  • If Mysolution hours are also pushed into TimeTracking as claims (source: "import", e.g. via POST /claims/batch), differences create conflicts that the office resolves.

8. Testing checklist

  • Running the same placement upsert twice returns the same ids with created: false.
  • Ending a placement in Mysolution deactivates it in TimeTracking.
  • GET /days incremental poll picks up a re-approved day exactly once per version.
  • Webhook receiver rejects a bad signature and ignores a replayed event id it already processed.

9. Troubleshooting

HTTP / code Message Fix
422 invalid Person and client must exist (by id or external ref) before the placement Upsert person and client with the same source first.
422 invalid Invalid cursor Restart pagination without cursor.
422 invalid Idempotency-Key was already used for a different request Use a new key per distinct request (8–200 chars).
404 not_found Not found GET /{resource}/{id} with a wrong or foreign id.
409 conflict N registration(s) exist after Hours after new end date.
409 conflict Client has placements; deactivate those instead DELETE /clients/{id} only works without placements.
403 forbidden API key is read-only / Admin role required write key for upserts; webhooks need an admin user or an admin-scope key.
429 rate_limited Rate limit of 600 requests per minute exceeded Wait 60 s (Retry-After); prefer batch.

10. Limits and known gaps

  • No API to create client contacts (approvers).
  • Upsert and PATCH bodies are full objects; omitted optional fields become null.
  • Plain POST /people emits no webhook; only by-external and batch-external upserts emit person.created.

Planned (not in API v1.1) — export-mapping CRUD via the API, OAuth2 for marketplace apps.

11. Build it with Claude

Build a Mysolution (Salesforce) ↔ TimeTracking sync.
- Source: Salesforce placement, candidate and account records (ask me for object/field API names; mark them TODO).
- In: PUT /api/v1/clients/by-external/mysolution/{AccountId},
  PUT /api/v1/people/by-external/mysolution/{CandidateId}, PUT /api/v1/placements/by-external/mysolution/{PlacementId}
  with personExternalRef/clientExternalRef. Nightly: the same via POST /api/v1/{people|clients|placements}/batch (≤500 rows).
  Ended placements → DELETE /api/v1/placements/{id}. Send an Idempotency-Key per write.
- Out: poll GET /api/v1/days?status=approved,exported&updated_since=<last run>&limit=500 (follow next_cursor) and upsert
  Mysolution timesheet lines keyed on day id. On export.completed, download GET /api/v1/exports/runs/{runId}/file.
- Respect 429 Retry-After; log per-row batch errors without stopping.