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

# From a User Import File to an API Synchronization

> Replace your user import file with an API synchronization, and keep the full history of employments and job positions.

If you update employee data through a **user import file**, whether you upload a CSV or Excel file manually or drop it on an SFTP server for automatic import, each line of that file describes an employee as they are today.

The file import only updates an employee's **current** employment and position. A new manager or department overwrites the previous one without recording when the change happened, and when a former employee returns, their past employment is overwritten.

The **Lucca API** preserves this history. An employee can have several successive employments, and each employment can have several successive job positions, each with its own start date. Instead of overwriting a column, the integration you build on the Lucca API creates a dated record for each change.

> [!NOTE]
>
> The write endpoints described in this guide are in **beta**. Their contract may change before general availability.

## Quick Start (TL;DR)

**1. Set up API access**

Create an OAuth 2.0 client with the `readwrite` scopes listed under [Required Scopes](#required-scopes), and send `Api-Version: 2024-11-01` with every request.

**2. Look up each employee by login**

`GET /lucca-api/employees?remoteId={login}` tells you whether a line refers to a new or an existing employee.

**3. Create new employees in a single call**

`POST /lucca-api/employees` creates the employee along with their first employment and first job position, in a single transaction.

**4. Date every position change**

A change of manager, department, occupation category or business establishment means a new job position: call `POST /lucca-api/job-positions` with the date the change takes effect.

**5. Create a new employment for each new contract**

When a former employee returns, add a new employment to their existing record with `POST /lucca-api/employments`.

**6. Write the remaining columns as employee attributes**

The second manager, seniority date, public holidays calendar, work cycles, leave profile and your organization's extensions are all written through `/lucca-api/employee-attributes`.

***

## What Changes Between a File Import and the API

| When your file contains... | The file import... | With the Lucca API |
| :- | :- | :- |
| A new employee | Creates the employee | `POST /lucca-api/employees` |
| A new manager, department, occupation category or establishment | Overwrites the current position and keeps its start date | `POST /lucca-api/job-positions`, effective from the date of the change |
| A contract end date | Updates the current contract | `PATCH /lucca-api/employments/{id}` |
| New contract dates for a former employee | Overwrites their last contract | `POST /lucca-api/employments`, keeping the previous contract in their history |
| A new name, email address or employee number | Updates the employee | `PATCH /lucca-api/employees/{id}` |

The API behaves differently from the file import in four ways:

* **One line, several calls.** A line in your file used to trigger a single update. With the API, it takes one call per record that changes: the employee, an employment, a job position or an attribute.
* **No upsert.** Your integration reads the current data in Lucca, compares it with the line, and only sends the calls it needs.
* **IDs instead of names and codes.** Your file refers to departments, establishments and managers by name, code or login. The API expects their `id`: see [Translate Names and Codes Into IDs](#translate-names-and-codes-into-ids).
* **Explicit dates.** Your file reflects the situation on the day it was generated. The API needs the date each change takes effect. If your source system only exports current data, use the export date.

> [!TIP]
>
> **Migrate progressively.** You can keep the file import running while you move columns to the API one at a time. Remove each column from your file as soon as your integration writes it: once a value has been written outside the import, the import no longer overwrites it for that employee, so a column sent through both channels would silently drift apart.

***

## Required Scopes

A single OAuth 2.0 token can hold every scope your integration needs.

| Operation | Required scopes |
| :- | :- |
| `POST /lucca-api/employees` | `employees.readwrite`, `employments.readwrite`, `job-positions.readwrite` |
| `PATCH /lucca-api/employees/{id}` | `employees.readwrite` |
| `POST /lucca-api/employments` | `employments.readwrite`, `job-positions.readwrite` |
| `PATCH /lucca-api/employments/{id}` | `employments.readwrite` |
| `POST` and `PATCH /lucca-api/job-positions` | `job-positions.readwrite` |
| `PATCH /lucca-api/employee-personal-records/{id}` | `employee-personal-records.readwrite` |
| `POST` and `PATCH /lucca-api/employee-attributes` | `employee-attributes.readwrite` |

> [!NOTE]
>
> Scopes grant access to endpoints. Your integration also needs access to the HR file sections that contain the data it writes.

> **[Authentication](/documentation/using-api/authentication.md)**
>
> How to obtain and use OAuth 2.0 bearer tokens.

***

## Mapping Your Columns to the API

This table lists the column names recognized by the file import, in English and French. Your file may use any of them.

| Column in your file | Lucca API resource | Field or definition ID |
| :- | :- | :- |
| `login` | `employee` | `remoteId` |
| `mail` | `employee` | `email` |
| `lastName`, `nom` | `employee` | `familyName` |
| `firstName`, `prénom` | `employee` | `givenName` |
| `employeeNumber`, `matricule` | `employee` | `employeeNumber` |
| `legalEntity`, `société`, `entitycode` | `job-position` | `businessEstablishment` |
| *(the legal entity of that establishment)* | `employment` | `legalEntity` |
| `department`, `service` | `job-position` | `department` |
| `manager`, `validateur` | `job-position` | `manager` |
| `csp` | `job-position` | `occupationCategory` |
| `jobTitle` | `job-position` | `jobTitle` |
| `dtContractStart` | `employment` | `start.date` |
| `dtContractEnd` | `employment` | `end.date` |
| `workContractType`, `typecontrat` | `employment` | `template` |
| `birthDate` | `employee-personal-record` | `birthDate` |
| `manager2`, `validateur2` | `employee-attribute` | `user.manager2Id` |
| `seniorityDate` | `employee-attribute` | `user.seniorityDate` |
| `calendar`, `calendrier` | `employee-attribute` | `user.calendarId` |
| `profile_figgo`, `profil` | `employee-attribute` | `user.figgoProfile` |
| `workCycles`, `horaires`, with their `.start` and `.end` columns | `employee-attribute` | `user.workCycles` |
| `e_xxx` (extensions) | `employee-attribute` | Same ID, e.g. `e_megacode` |
| `rolePrincipal`, `rôle` | *(not available)* | See [Roles](#roles) |

> [!WARNING]
>
> In the file import, the `legalEntity` column refers to what the Lucca API calls a **business establishment**. In the API, `legalEntity` is the legal entity of the employment, and it must be the legal entity that the first job position's business establishment belongs to.

> [!NOTE]
>
> Every employment requires an employment `template`, which corresponds to the contract type in your file. If your file has no contract type column, define a rule before you build your integration, for example one template per legal entity or per occupation category.

### Translate Names and Codes Into IDs

Build a lookup table once per synchronization run instead of making one call per line:

| When your file refers to... | Fetch the list with | Match on |
| :- | :- | :- |
| An establishment | `GET /lucca-api/business-establishments` | `name`, or `remoteId` for its code |
| A legal entity | `GET /lucca-api/legal-entities` | `legalName` |
| A department | `GET /lucca-api/departments` | `name`, or `remoteId` for its code |
| An occupation category | `GET /lucca-api/occupation-categories` | `title` |
| A contract type | `GET /lucca-api/employment-templates` | `title` |
| A manager | `GET /lucca-api/employees?remoteId={login}` | `remoteId` |

> [!TIP]
>
> Identify managers by login rather than by name, since two employees can share the same name. If a manager joins in the same batch, create them before the employees who report to them.

> [!TIP]
>
> Handle [pagination](/documentation/using-api/paging.md) on every collection. Pages hold up to 1,000 items.

***

## Processing a Line From Your File

Each line is matched to an employee by login, then compared with that employee's employments and job positions.

```mermaid theme={null}
flowchart TD
  A[Line from your file] --> B{GET /employees?remoteId=login}
  B -- Not found --> C[1. Create the employee]
  B -- Found --> D{Does an employment cover the line's contract start date?}
  D -- No, the last one has ended --> E[2. Create an employment]
  D -- Yes --> F{Has the contract end date changed?}
  F -- Yes --> G[4. Update the employment]
  F -- No --> H{Has any position column changed?}
  G --> H
  H -- Yes --> I[3. Create a job position]
  H -- No --> J[Position unchanged]
  C --> K[5 and 6. Personal record and remaining columns]
  E --> K
  I --> K
  J --> K
```

***

## Use Cases

### 1. Create an Employee

`POST /lucca-api/employees` creates the employee along with their first employment and first job position, in a single transaction. The first job position is nested inside `firstEmployment`. If any part of the request is invalid, nothing is created.

```http theme={null}
POST /lucca-api/employees HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Content-Type: application/json
```

```json theme={null}
{
  "givenName": "Camille",
  "familyName": "Martin",
  "remoteId": "cmartin",
  "email": "camille.martin@example.org",
  "phoneNumber": null,
  "employeeNumber": "000412",
  "portrait": null,
  "firstEmployment": {
    "template": { "id": "3" },
    "legalEntity": { "id": "1" },
    "start": { "date": "2026-11-02" },
    "end": null,
    "remoteId": null,
    "document": null,
    "firstJobPosition": {
      "businessEstablishment": { "id": "4" },
      "department": { "id": "27" },
      "manager": { "id": "118" },
      "occupationCategory": { "id": "2" },
      "jobQualification": null,
      "workingTimeArrangement": null,
      "jobTitle": "Support Technician",
      "remoteId": null,
      "document": null
    }
  }
}
```

The response contains the new employee and their `id`. Use this `id` to write the rest of the line: the birth date (see [Update Identity and Personal Data](#5-update-identity-and-personal-data)) and the remaining columns (see [Remaining Columns](#6-remaining-columns)).

> [!NOTE]
>
> If you leave out the public holidays calendar, occupation category or role, the defaults configured for the business establishment apply.

> [!WARNING]
>
> Employees cannot be deleted through the API. If a call fails after `POST /lucca-api/employees` has succeeded, retry that call instead of creating the employee again: their login is already in use.

### 2. Create a New Employment

A new work contract means a new employment on the existing employee record, and the previous employment stays in their history. This applies in two situations:

* **A former employee returns** after their previous contract has ended.
* **An employee moves to another legal entity**: end the current employment, then create the new one.

**1. Look up the employee by login**

```http theme={null}
GET /lucca-api/employees?remoteId=cmartin HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
```

Do not create the employee again: their login is already in use.

**2. Check the dates**

List their employments with `GET /lucca-api/employments?employee.id={employeeId}`. The new employment must start after the previous one ends. Overlapping employments are rejected with `urn:employees:error:employment-overlap`.

Creating an employment does not end the previous one. If the previous employment has no end date, set it first with `PATCH /lucca-api/employments/{id}` (see [use case 4](#4-correct-an-existing-employment-or-job-position)).

**3. Create the employment**

```http theme={null}
POST /lucca-api/employments HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Content-Type: application/json
```

```json theme={null}
{
  "employee": { "id": "412" },
  "template": { "id": "3" },
  "legalEntity": { "id": "1" },
  "start": { "date": "2027-09-01" },
  "end": null,
  "remoteId": null,
  "document": null,
  "firstJobPosition": {
    "businessEstablishment": { "id": "4" },
    "department": { "id": "27" },
    "manager": { "id": "205" },
    "occupationCategory": { "id": "2" },
    "jobQualification": null,
    "workingTimeArrangement": null,
    "jobTitle": "Support Technician",
    "remoteId": null,
    "document": null
  }
}
```

Nothing carries over from the previous employment, so send the full first job position.

**4. Update the columns that changed between the two employments**

For example, the seniority date, work cycles or leave profile.

### 3. Create a New Job Position

When an employee's manager, department, occupation category or business establishment changes under the same contract, create a new job position. It starts on the date the change takes effect. Lucca ends the previous position the day before, and both appear in the employee's career history.

**1. List the job positions of the employment**

```http theme={null}
GET /lucca-api/job-positions?employment.id={employmentId}&limit=100 HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
```

Keep the position with the latest `startsOn`, which may be in the future.

**2. Compare it with the line from your file**

If nothing differs, there is nothing to send. Otherwise, copy every value from the latest position and replace the ones that your file changes.

**3. Create the new job position**

```http theme={null}
POST /lucca-api/job-positions HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Content-Type: application/json
```

```json theme={null}
{
  "employment": { "id": "5310" },
  "startsOn": "2026-12-01",
  "businessEstablishment": { "id": "4" },
  "department": { "id": "27" },
  "manager": { "id": "205" },
  "occupationCategory": { "id": "2" },
  "jobQualification": { "id": "9" },
  "workingTimeArrangement": { "id": "1" },
  "jobTitle": "Support Technician",
  "remoteId": null,
  "document": null,
  "notes": null
}
```

> [!WARNING]
>
> Send **every** field of the position, including those that your file does not contain, such as `workingTimeArrangement` or `jobQualification`. Any field you leave out will be empty on the new position.

The API rejects a new job position in the following cases:

| Problem type | Cause | What to do |
| :- | :- | :- |
| `urn:employees:error:job-position-is-same-as-previous` | Nothing differs from the previous position | Skip the line, since there is nothing to record |
| `urn:employees:error:job-position-chronology-impact` | Another position in the same employment starts on or after this date | Correct that position instead (see [use case 4](#4-correct-an-existing-employment-or-job-position)), or flag the line |
| `urn:employees:error:job-position-starts-on-not-in-employment-period` | The date falls outside the employment | Flag the line |

### 4. Correct an Existing Employment or Job Position

Use `PATCH` to fix a record that was entered incorrectly, or to end a contract. A correction does not add an entry to the career history. `PATCH` only updates the fields you send, and any field sent as `null` is cleared.

**End or correct an employment**

```http theme={null}
PATCH /lucca-api/employments/{id} HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Content-Type: application/json
```

```json theme={null}
{ "end": { "date": "2027-05-01" } }
```

The last job position ends on the same date. Sending `"end": null` removes the end date, which undoes a departure entered by mistake. You can correct the start date, template, reference (`remoteId`) and document the same way.

The API rejects the update if a job position starts after the new end date, or if the probationary period ends after it. The legal entity of an existing employment cannot be changed: create a new employment instead.

**Correct a job position**

```http theme={null}
PATCH /lucca-api/job-positions/{id} HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Content-Type: application/json
```

```json theme={null}
{ "department": { "id": "31" } }
```

The position keeps its dates, and the new value applies to its entire period. To record a change from a specific date, create a new job position instead.

### 5. Update Identity and Personal Data

Name, email address, login and employee number are fields of the employee.

```http theme={null}
PATCH /lucca-api/employees/{id} HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Content-Type: application/json
```

```json theme={null}
{
  "givenName": "Camille",
  "familyName": "Martin-Durand",
  "email": "camille.martin-durand@example.org",
  "employeeNumber": "000412",
  "phoneNumber": null,
  "remoteId": "cmartin",
  "portrait": null
}
```

Employee numbers must be unique within a business establishment.

The birth date is stored on the personal record, which is created empty along with the employee. The personal record shares the employee's `id`:

```http theme={null}
PATCH /lucca-api/employee-personal-records/{employeeId} HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Content-Type: application/json
```

```json theme={null}
{ "birthDate": "1991-04-17" }
```

### 6. Remaining Columns

All other columns are written through `/lucca-api/employee-attributes`. Each value is an attribute with its own `id`.

**1. Fetch the attribute**

```http theme={null}
GET /lucca-api/employee-attributes?definition.id=user.manager2Id&employee.id={employeeId} HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
```

**2. Update its value**

```http theme={null}
PATCH /lucca-api/employee-attributes/{id} HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Content-Type: application/json
```

```json theme={null}
{ "value": { "id": "205" } }
```

The `value` must match the definition's `schema`, which you can fetch with `GET /lucca-api/employee-attribute-definitions/{id}`. See [Understanding the `value` Property](/api-reference/latest/employee-attributes/get-started#understanding-the-value-property). Where your file uses a name (calendar, work cycle, leave profile), the API expects the corresponding `id`.

| Column in your file | Definition ID | How to write it |
| :- | :- | :- |
| `manager2` | `user.manager2Id` | `PATCH` the attribute, which exists for every employee |
| `seniorityDate` | `user.seniorityDate` | `PATCH` the attribute, which exists for every employee |
| `calendar` | `user.calendarId` | `PATCH` the attribute, which exists for every employee |
| `profile_figgo` | `user.figgoProfile` | `PATCH` the attribute, see [Leave profile](#leave-profile) |
| `workCycles` | `user.workCycles` | `POST` one attribute per period, `PATCH` to change it |
| `e_xxx` | Same ID | `POST` the first value, then `PATCH` it |

#### Work Cycles

> [!NOTE]
>
> Work cycles are being progressively replaced by [working time arrangements](/api-reference/latest/working-time-arrangements/working-time-arrangement.md), which are set on the job position (`workingTimeArrangement`). If your account already uses working time arrangements, write them on the job position instead.

Your file can contain several `workCycles` periods, each with its own `.start` and `.end` columns. In the API, each period is a separate attribute. An `end` of `null` means the period is open-ended.

```http theme={null}
POST /lucca-api/employee-attributes HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Content-Type: application/json
```

```json theme={null}
{
  "definition": { "id": "user.workCycles" },
  "employee": { "id": "412" },
  "value": { "id": "6" },
  "applicability": { "start": "2026-11-02", "end": null }
}
```

An employee's periods cannot overlap (`urn:employees:error:work-cycles-overlap`). To switch to a different cycle, first set the `end` of the current period with `PATCH`, then create the next one.

#### Extensions

Extensions created by your organization keep the ID of their `e_xxx` column. Create the first value with `POST`:

```json theme={null}
{
  "definition": { "id": "e_megacode" },
  "employee": { "id": "412" },
  "value": "MC-2231"
}
```

If the employee already has a value, the API returns `urn:employees:error:attribute-already-set-for-user`. In that case, fetch its `id` and use `PATCH` instead. To list the extensions on your account, call `GET /lucca-api/employee-attribute-definitions?category=extension`.

#### Leave Profile

`user.figgoProfile` contains an `appInstanceId` and a `profileId`. It can only be updated for employees who already have a leave profile, and the new profile must be available for the employee's business establishment.

***

## Not Yet Available

### Roles

Roles cannot be assigned through the Lucca API yet. When an employee is created, they receive the default role configured for their business establishment, and creation fails if no default role is configured. To assign a different role, use the Lucca interface.

***

## Best Practices

* **Compare before you write.** Read the employee, their employments and their latest job position, and only send the calls that change something. Your integration can then run as often as you need without adding duplicate positions to the history.
* **Follow the order of operations.** Create managers before the employees who report to them, employments before their job positions, and employees before their attributes.
* **Process changes in chronological order.** A job position cannot be inserted before an existing position in the same employment.
* **Handle errors by problem type.** Every `4xx` response is a Problem JSON object whose `type`, or the `type` of its field-level `errors`, identifies the cause. Flag rejected lines instead of retrying them unchanged.
* **Stay within the rate limits.** See [Limits](/documentation/limits.md) to plan a full synchronization.

***

## Further Reading

> **[Migrating From V3 Users](/api-reference/legacy/directory/guides/users-migration.md)**
>
> The Lucca API data model, and how to read employee data.

> **[Get Started With Employee Attributes](/api-reference/latest/employee-attributes/get-started.md)**
>
> Definitions, value schemas and write operations on employee attributes.

> **[Employment](/api-reference/latest/employments/employment.md)**
>
> Full reference for the `employment` resource.

> **[Job-Position](/api-reference/latest/employments/job-position.md)**
>
> Full reference for the `job-position` resource.


## Related topics

- [V3 Users to V5 Employees](/api-reference/legacy/directory/guides/users-migration.md)
- [Import user locations](/api-reference/legacy/timmi-office/guides/import-and-create-user-locations.md)
- [Import leaves](/api-reference/legacy/timmi-absences/imports/import-leaves.md)
- [Import entitlements](/api-reference/legacy/timmi-absences/imports/import-entitlements.md)
