> ## Documentation Index
> Fetch the complete documentation index at: https://developers.jobhandy.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Synchronize employees

> Design a resumable employee synchronization with stable identity, explicit placement, dry-run validation, partial updates, and reconciliation.

A reliable synchronization separates identity matching, tenant placement, validation, mutation, and checkpointing. Never create a new JobHandy employee on every import run.

## Source-of-truth model

Before implementation, assign ownership for every mapped field.

| Field group              | Recommended owner                                                    | Integration behavior                                                 |
| ------------------------ | -------------------------------------------------------------------- | -------------------------------------------------------------------- |
| Work email               | HR/customer process at creation; not patchable through this endpoint | Treat change as a separate business process                          |
| Employee number          | Customer HR system when stable                                       | Use as a matching key where uniqueness is guaranteed                 |
| Contact and address data | Agreed source system                                                 | Patch only changed supported fields                                  |
| `blocked`                | Agreed offboarding/account process                                   | Set explicitly; do not treat as deletion                             |
| Tenant                   | Placement process                                                    | Resolve on create; public patch does not transfer assigned employees |
| Division                 | HR organization source                                               | Patch within the same tenant or clear with `null`                    |
| JobHandy `id`            | JobHandy                                                             | Persist after match or create and use for later requests             |

## Matching order

1. Stored JobHandy employee ID
2. Stable `employeeNumber` when the source guarantees uniqueness
3. Another explicitly agreed unique mapping
4. Email only when the customer guarantees it is immutable and unique

If a query returns zero or more than one plausible match, stop automated creation and route the record for reconciliation.

## Synchronization swimlane

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
sequenceDiagram
    participant HR as HR system
    participant INT as Integration
    participant API as JobHandy API
    participant MAIL as Password email process

    HR->>INT: Employee delta and source version
    INT->>API: Find by stored ID or employeeNumber
    alt Existing employee
        API-->>INT: Current employee
        INT->>INT: Compare integration-owned fields
        opt Changes detected
            INT->>API: PATCH ?dryRun=true
            API-->>INT: Projected employee
            INT->>API: PATCH
            API-->>INT: Persisted employee + request ID
        end
    else No employee exists
        INT->>API: POST ?dryRun=true
        API-->>INT: Projected employee
        INT->>API: POST
        API-->>INT: Created employee ID + request ID
        API->>MAIL: Send password setup email
    else Ambiguous match
        INT->>INT: Stop and create reconciliation item
    end
    INT->>HR: Persist JobHandy ID, version, and result
```

## Resolve placement

* Send `X-Tenant-ID` when the key includes multiple possible tenants.
* Send `divisionId` when the selected tenant does not resolve one permitted division.
* Use only a division inside the target tenant and API-key scope.
* `divisionId: null` removes the division assignment but does not transfer the employee.

## Find an employee

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --get 'https://api.jobhandy.io/v1/employees' \
  --header 'X-API-Key: YOUR_API_KEY' \
  --header 'X-Tenant-ID: {{tenantId}}' \
  --data-urlencode 'filter=employeeNumber=EMP-1042'
```

Persist the returned JobHandy `id` after a successful match.

## Create safely

<Steps>
  <Step title="Confirm no existing match">
    Search by the stored JobHandy ID or stable employee number.
  </Step>

  <Step title="Validate placement and payload">
    Call `POST /employees?dryRun=true` with the final headers and body.
  </Step>

  <Step title="Apply the create">
    Send the same request without `dryRun=true`.
  </Step>

  <Step title="Persist the real ID">
    Store only the ID returned by the non-dry-run `201` response.
  </Step>

  <Step title="Record the side effect">
    A successful create sends a password setup email. Do not create duplicate accounts to repeat that email.
  </Step>
</Steps>

## Patch only changed fields

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "phoneNumber": "+49 221 1234567",
  "divisionId": "68920e08eeaea4f2301eecb3"
}
```

Nullable patch properties can be cleared with `null`. Omit properties that should remain unchanged.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "phoneNumber": null,
  "divisionId": null
}
```

## Checkpoint model

Persist at least:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
sourceEmployeeId
jobHandyEmployeeId
sourceVersion or watermark
lastSuccessfulAtUtc
lastRequestId
lastResult
```

## Timeout reconciliation

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
flowchart TD
    A[Create or patch timed out] --> B[Read/search employee]
    B --> C{Desired state present?}
    C -- Yes --> D[Record success and checkpoint]
    C -- No --> E{Resource can be matched safely?}
    E -- Yes --> F[Re-evaluate and retry with new request ID]
    E -- No --> G[Manual reconciliation]
```

## Common failures

| Error                          | Meaning                                                  | Action                                     |
| ------------------------------ | -------------------------------------------------------- | ------------------------------------------ |
| `DUPLICATE_RESOURCE`           | A uniqueness rule is already satisfied by another record | Locate and reconcile the existing employee |
| `EMPLOYEE_PLACEMENT_AMBIGUOUS` | Division-restricted scope does not resolve one target    | Send an allowed `divisionId`               |
| `TENANT_CONTEXT_AMBIGUOUS`     | More than one tenant is possible                         | Send `X-Tenant-ID`                         |
| `INVALID_REFERENCE`            | Referenced tenant or division is unavailable             | Refresh scope and references               |
| `409` or `422` after dry run   | State changed or final validation differs                | Re-read and revalidate; do not blind retry |
