# Features
Source: https://developers.luccasoftware.com/api-reference/legacy/api-generations
Features of the Legacy API, and the differences between v3 and v4 API generations.
These Legacy APIs may suffer from inconsistencies between endpoints, most likely due to them adhering
to a different standard: either v3 or v4.
The easiest way of figuring out which standard a Legacy API endpoint adheres to si to check its URL:
* if it starts with `/api/v3/**`, then it is definitely a "v3" API.
* if it contains the name of an application, e.g. `/timmi-absences/api/**`, then it most likely is a "v4" API.
Here is a recap of behavioral differences between both standards:
| Feature | v3 | v4 |
| :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Expanding responses** | By default, only 3 properties are returned: `id`, `name` and `url`. You may include more properties, and even those of related resources, through the use of the `?fields` query parameter.
Nested objects properties can be included like this: `?fields=id,nestedObject[a,b,c]`. | All properties are always returned, except the total number of items of a collection, which can be included with the `?fields.root=count` query parameter. You have no control over this. |
| **Paging** | Paging is handled through two comma-separated parameters in a single query parameter: `?paging={offset},{page_size}`. `{offset}` is the index of the first item to retrieve in the collection. For example, to retrieve the first page of 100 items: `?paging=0,100`.
The total number of items across all pages can be retrieved through `?fields=collection.count`. | Paging is handled through 2 query parameters: `?limit={page_size}` and `?page={page_number}`. For example, to retrieve the first page of 100 items: `?limit=100&page=1`.
Retrieve the total number of items across all pages through `?fields.root=count`. |
| **Filtering** | You can filter out items of a collection by most of their properties.
Strict equality with `?{propertyName}={value}` or inequality through `?{propertyName}=notequal,{value}`.
Date comparisons: `?{dateProperty}=since,{date}` or `?{dateProperty}=until,{date}` or `?{dateProperty}=between,{start},{end}`.
Numbers comparisons through `?{numberProperty}=greaterthan,{value}` or `?{numberProperty}=greaterthanorequal,{value}` or `?{numberProperty}=lessthan,{value}` or `?{numberProperty}=lessthanorequal,{value}`. | The only filters that are supported are documented in the API reference as query parameters. |
| **Sorting** | You can sort on most properties through `?orderBy={propertyName},{direction}` where `{direction}` is either `"asc"` or `"desc"`. | Identical to the [sorting feature in the Lucca API](../../documentation/using-api/sorting). You can sort on a subset of properties through the `?sort` query parameter, those that are documented in the API reference. |
| **Representations** | The representation of the resource is contained in the `data` property of the root JSON object.
Items of a collection are entries of the `data.items` array. | The representation of the resource is at the root of the JSON object.
Items of a collection are entries of the `items` array. |
# Authentication
Source: https://developers.luccasoftware.com/api-reference/legacy/authentication
Learn how to authenticate on a Legacy API.
## Authenticating
Authentication is handled through the use of an API key, which can be managed in your
[Lucca account settings](https://support.lucca.fr/hc/en-us/articles/115000084851).
Each key is given its owned dedicated role that dictates the depth and breadth of its access rights. This dedicated
role has the same name as the API key by default, and is listed at the very bottom of the list of roles in the
corresponding administration interface (i.e. /organization/structure/roles).
Once you have an API key, you must reference it on all requests against the Legacy APIs through the `Authorization`
HTTP header in the following manner:
```
Authorization = lucca application={API_KEY}
```
Make sure to keep API keys secure. Delete it at once if you suspect it's been
compromised and create a new one. You can also periodically change the API key.Please not that if authentication was unsuccessful (most likely due to
an invalid API key), then the server will return a `401 Unauthorized` HTTP
status code.It is considered good practice to **create a new API key for each integration
project**. It gives you the opportunity to finely tune access scopes, and limits
security risks in case of a key value leak. In other words, try to attach the minimum
required access scope to any key. There is a guide about setting up access scopes.Make sure to set the "technical contact" on all keys (email of someone tech-savy
among yourselves). This will be our primary contact point in case of technical problems
or API changes.
```http HTTP theme={null}
GET /api/v3/users HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
```
## CORS Policy
Our Cross-origin Ressource Sharing policy doesn't allow API call from web browser using
a different domains than `ilucca.net` or `ilucca-test.net`.
If you need to automate processes using a Lucca API Key, you should do it:
* throught a script
* from an API Platform (e.g. Postman or Azure API Management)
* from the backend of your own application
# Legacy API Changelog
Source: https://developers.luccasoftware.com/api-reference/legacy/changelog
#### Added
* Documentation for the new `job-offers` API endpoint.
* Documentation for the new (job-offer) `applications` API endpoint.
#### Deprecation Notice
The V3 departments API is now deprecated. Sunset ETA is: **September 2026**.
Refer to the [migration guide](/api-reference/legacy/organization/departments/migration-guide) for more information.
#### Added
Guides.
#### Added
Documentation for how to download expense receipts.
#### Added
Documentation for the `expense-temp-items` endpoints.
#### Added
Documentation for the `sick-leave-certificate` endpoints.
#### Added
Documentation for the `project-services` endpoints.
Documentation for the project `status-changes` endpoints.
#### Added
Documentation for the main Lucca Training API endpoints.
#### Added
Documentation for the main Lucca Compensation API endpoints.
#### Added
New query parameter on the `project-financials` endpoint of Lucca Project to support filtering on the project owner department.
#### Added
Added an example for the body of the "import-leaves" request. Ideally, it should now be much more intelligible.
#### Added
Documentation for the main Lucca Office API endpoints.
#### Fixed
Publish internal models in order to fix some errors.
#### Added
Documentation for the UserDates API endpoint.
#### Added
A new endpoint `/api/v3/expenseClaims` has been added to get the list of expense claims through different query parameters.
#### Deprecated
The old v3 API endpoint `/api/v3/services/importLeavePeriods` will no longer be supported in the future.
#### Added
A new endpoint replaces the deprecated `importLeavePeriods`: `/figgo/api/imports/v1.0/leavePeriods`. Main changes in this new endpoint are: (1) the `?type=csv` query parameter is no longer required or even used and (2) it now supports an async mode through the `?async=true` query parameter. By default, the process is synchronous.
In case of an async import, a new endpoint enables you to track its progress: `/figgo/api/imports/v1.0/leavePeriods/{summaryId:guid}/progress`.
# The expense-claim-item resource
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/expenseclaimitems/expense-claim-item
cleemy-expenses-v3
# Get an ExpenseClaimItem by id
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/expenseclaimitems/get-an-expenseclaimitem-by-id
/openapi-specs/cleemy-expenses-v3.yaml get /api/v3/expenseClaimItems/{expenseClaimItemId}
Retrieve an expense claim item by its identifier.
# List ExpenseClaimItems
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/expenseclaimitems/list-expense-claim-items
/openapi-specs/cleemy-expenses-v3.yaml get /api/v3/expenseClaimItems
Retrieve a list of `expenseClaimItems`.
The `purchasedOn` query parameter can operate comparisons with a given date-time value:
- `?purchasedOn=2021-01-01`: strict equality.
- `?purchasedOn=since,2021-01-01`: greater than or equal.
- `?purchasedOn=until,2021-01-01`: lower than or equal.
- `?purchasedOn=between,2021-01-01,2021-01-31`: comprised between two dates.
# Create a new ExpenseClaim
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/expenseclaims/create-a-new-expenseclaim
/openapi-specs/cleemy-expenses-v3.yaml post /api/v3/expenseClaims/creation
When a user wants to declare his expenses, he creates an `ExpenseClaim`.
An `ExpenseClaim` is created by regrouping one or more `ExpenseTempItems` and converting them into ExpenseClaimItems.
Once created, an `ExpenseClaim` has to be approved by his manager.
Multiple `ExpenseClaims` can be created through a single request.
# The expense-claim resource
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/expenseclaims/expense-claim
cleemy-expenses-v3
# List ExpenseClaims
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/expenseclaims/list-expenseclaims
/openapi-specs/cleemy-expenses-v3.yaml get /api/v3/expenseClaims
Retrieve a list of `ExpenseClaims`.
The `declaredOn` query parameter can operate comparisons with a given date-time value:
- `?declaredOn=2021-01-01`: strict equality.
- `?declaredOn=since,2021-01-01`: greater than or equal.
- `?declaredOn=until,2021-01-01`: lower than or equal.
- `?declaredOn=between,2021-01-01,2021-01-31`: comprised between two dates.
# Create a new temporary expense
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/expenses/create-a-new-temporary-expense
/openapi-specs/cleemy-expenses-v3.yaml post /api/v3/expenseTempItems
Create a new temporary-expense-item.
**Attaching a receipt**: if the expense nature requires a receipt, you must first upload the file via `POST /lucca-files/api/uploads`, then create an `ExpenseReceipt` via `POST /api/v3/expenseReceipts`, and finally reference the receipt's `id` in the `expenseReceipts` array. Do **not** pass the file upload ID directly — see the [Attaching receipts guide](/api-reference/legacy/cleemy-expenses/guides/attaching-receipts) for the full workflow.
# The expense-temp-item resource
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/expenses/expense-temp-item
cleemy-expenses-v3
# Get an ExpenseTempItem by id
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/expenses/get-an-expensetempitem-by-id
/openapi-specs/cleemy-expenses-v3.yaml get /api/v3/expenseTempItems/{expenseTempItemId}
Retrieve an temporary expense item by its identifier.
# List ExpenseTempItems
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/expenses/list-expensetempitems-temporary-expense
/openapi-specs/cleemy-expenses-v3.yaml get /api/v3/expenseTempItems
Retrieve a list of `ExpenseTempItems`.
The `purchasedOn` query parameter can operate comparisons with a given date-time value:
- `?purchasedOn=2021-01-01`: strict equality.
- `?purchasedOn=since,2021-01-01`: greater than or equal.
- `?purchasedOn=until,2021-01-01`: lower than or equal.
- `?purchasedOn=between,2021-01-01,2021-01-31`: comprised between two dates.
# Update an ExpenseTempItem by id
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/expenses/update-an-expensetempitem-by-id
/openapi-specs/cleemy-expenses-v3.yaml put /api/v3/expenseTempItems/{expenseTempItemId}
Update a temporary expense item by its identifier.
# Create an export and retrieve export content
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/exports/post-create-expense-accounting-export
/openapi-specs/cleemy-expenses-v3.yaml post /cleemy/services/v3/createExpenseAccountingExport
Create a new export and retrieve the export content directly in the response body for a given business-establishment.
**In the v3 API model, the business-establishment is often called "legal-entity". As a result, the `legalEntityId` query parameter corresponds to the ID of a business-establishment.**
The response contains all the exported entry lines for the given business-establishment.
The date query parameters can operate comparisons with a given date-time value. For examples :
- `?approvedOn=2021-01-01`: strict equality.
- `?approvedOn=since,2021-01-01`: greater than or equal.
- `?approvedOn=until,2021-01-01`: lower than or equal.
- `?approvedOn=between,2021-01-01,2021-01-31`: comprised between two dates.
# Learn about expenses custom fields
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/guides/about-custom-fields
When configuring Lucca Expenses, you may add custom fields to `expense-temp-items` and `expense-claim-items`.
## Retrieving the list of customFields enabled for an expense nature
Custom fields activation depends on the expense nature. For example, custom field "Bill back client (bool)" may be activated for travel expenses, but not for team building expenses.
```json List natures w/ customFields theme={null}
GET /api/expenseNatures?fields=id,name,url,customFields.id,customFields.name,customFields.url,customFields.type.tag,customFields.case.tag,customFields.compulsory,customFields.minLength,customFields.maxLength,customFields.defaultValue HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
< Response
{
"data": [
{
"id": 1,
"name": "Diner",
"url": "https://example.ilucca.net/api/expenseNatures/49"
"customFields": [
{
"id": "1",
"name": "Bill back client",
"url": "https://example.ilucca.net/api/expenseCustomFields/1",
"type": {
"tag": "Bool"
},
"case": {
"tag": "Insensitive"
},
"compulsory": false,
"minLength": 0,
"maxLength": null,
"defaultValue": "false"
}
]
}
]
}
```
Each custom field is typed, as indicated by the `customField.type.tag` discriminator:
| Type.Tag | Expected value (code) - actual values are always strings | Example |
| :----------- | :--------------------------------------------------------------------------------- | :-------------------- |
| `String` | Any text string as long as it conforms to `MinLength` and `MaxLength`. | `"test"` |
| `Bool` | Stringified boolean. | `"true"` or `"false"` |
| `Int` | Stringified integer | `"48"` |
| `Float` | Stringified float. | `"18.25"` |
| `Digits` | String of numbers, that must potentially conform to a `minLength` and `maxLength`. | `"0495846"` |
| `ListValues` | String among a list (i.e. enum; refer to `values` dictionary property). | `"foo"` |
```yaml Schema theme={null}
title: customField
type: object
properties:
id:
type: string
description: Unique identifier of the custom field. Keep in mind one custom field can apply to multiple expense natures.
name:
type: string
description: Name of the custom field
url:
type: string
format: uri
type:
type: object
properties:
tag:
type: string
enum: [String, Bool, Int, Float, Digits, ListValues]
description: |-
- `String`: any text string;
- `Bool`: stringified boolean value, i.e. `"true"` or `"false"`.
- `Int`: stringified integer value, i.e. `"234"`.
- `Float`: stringified float value, i.e. `"0.123"`.
- `Digits`: string of numbers, i.e. "0123456789".
- `ListValues`: string among an list of accepted values (i.e. enumeration).
case:
type: object
properties:
tag:
type: string
enum: [Insensitive, Uppercase, Lowercase]
description: Case sensitivity.
compulsory:
type: boolean
description: Whether a value MUST be set on expenses of this expense-nature or not.
default: false
minLength:
type: [integer, "null"]
minimum: 0
default: null
description: Minimum number of characters accepted for a value.
maxLength:
type: [integer, "null"]
minimum: 0
descritpion: Maximum number of characters accepted for a value.
defaultValue:
type: string
description: Explicit default for values.
values:
type: object
description: Dictionary valueCode => valueDisplayName. Lists the enum values for a `ListValues` typed custom field.
additionalProperties:
type: string
```
```json String theme={null}
{
"data": [
{
"id": 1,
"name": "Diner",
"url": "https://example.ilucca.net/api/expenseNatures/49",
"customFields": [
{
"id": "1",
"name": "Restaurant name",
"url": "https://example.ilucca.net/api/expenseCustomFields/1",
"type": {
"tag": "String"
},
"case": {
"tag": "Insensitive"
},
"compulsory": false,
"minLength": 0,
"maxLength": null,
"defaultValue": "",
"values": null
}
]
}
]
}
```
```json Bool theme={null}
{
"data": [
{
"id": 1,
"name": "Diner",
"url": "https://example.ilucca.net/api/expenseNatures/49",
"customFields": [
{
"id": "2",
"name": "Bill back client",
"url": "https://example.ilucca.net/api/expenseCustomFields/2",
"type": {
"tag": "Bool"
},
"case": {
"tag": "Insensitive"
},
"compulsory": false,
"minLength": 0,
"maxLength": null,
"defaultValue": "false",
"values": {
"true": "Yes",
"false": "No"
}
}
]
}
]
}
```
```json Digits theme={null}
{
"data": [
{
"id": 1,
"name": "Diner",
"url": "https://example.ilucca.net/api/expenseNatures/49",
"customFields": [
{
"id": "3",
"name": "Guests",
"url": "https://example.ilucca.net/api/expenseCustomFields/3",
"type": {
"tag": "Int"
},
"case": {
"tag": "Insensitive"
},
"compulsory": false,
"minLength": null,
"maxLength": null,
"defaultValue": 0,
"values": null
}
]
}
]
}
```
```json Digits theme={null}
{
"data": [
{
"id": 1,
"name": "Diner",
"url": "https://example.ilucca.net/api/expenseNatures/49",
"customFields": [
{
"id": "4",
"name": "Phone number",
"url": "https://example.ilucca.net/api/expenseCustomFields/4",
"type": {
"tag": "Digits"
},
"case": {
"tag": "Insensitive"
},
"compulsory": false,
"minLength": 10,
"maxLength": 10,
"defaultValue": "false",
"values": null
}
]
}
]
}
```
```json ListValues theme={null}
{
"data": [
{
"id": 1,
"name": "Diner",
"url": "https://example.ilucca.net/api/expenseNatures/49",
"customFields": [
{
"id": "5",
"name": "Michelin Stars",
"url": "https://example.ilucca.net/api/expenseCustomFields/5",
"type": {
"tag": "ListValues"
},
"case": {
"tag": "Sensitive"
},
"compulsory": false,
"minLength": null,
"maxLength": null,
"defaultValue": "false",
"values": {
"one": "⭐",
"two": "⭐⭐",
"three": "⭐⭐⭐"
}
}
]
}
]
}
```
***
## Retrieving the customFields values for an expense
In order to retrieve the list of customField values for an `ExpenseTempItem` or an `ExpenseClaimItem`, simply add "customFields" to the list of fields requested through the `?fields` query parameter:
```http ExpenseTempItem theme={null}
GET /api/v3/expenseTempItems?fields=id,customFields HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
```
```http ExpenseClaimItem theme={null}
GET /api/v3/expenseclaimitems?fields=id,customFields HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
```
The `customFields` field you'll retrieve is a JSON object whose keys are the IDs of the `customField`.
```json Bool theme={null}
{
"id": 23,
"customFields": {
"2": {
"code": "false",
"name": "No"
}
}
}
```
```json ListValues theme={null}
{
"id": 23,
"customFields": {
"5": {
"code": "two",
"name": "⭐⭐"
}
}
}
```
***
# Attaching receipts to expenses
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-expenses/guides/attaching-receipts
Learn how to upload a receipt file and attach it to an expense when using the Expenses API.
When an expense nature requires a receipt (i.e. the receipt is mandatory), you must follow a **three-step process** to attach it. You cannot pass a file upload ID directly in the `expenseReceipts` array — doing so will result in a `403 Forbidden` error.
## Step 1 — Upload the file
First, upload the receipt file (image, PDF, etc.) using the [Files API](/api-reference/legacy/files/post-upload):
```http Upload a file theme={null}
POST /lucca-files/api/uploads HTTP/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
Content-Type: multipart/form-data; boundary=----FormBoundary
------FormBoundary
Content-Disposition: form-data; name="form"; filename="receipt.pdf"
Content-Type: application/pdf
< ./receipt.pdf
------FormBoundary--
```
```json Response (201 Created) theme={null}
{
"id": "a24f4279-6bb4-4e1c-9b40-a80a5d44b36d",
"name": "receipt.pdf",
"createdAt": "2025-06-15T10:30:00Z",
"deletedAt": null,
"contentLength": 95683,
"contentType": "application/pdf",
"extension": ".pdf",
"totalPages": 1
}
```
Save the returned `id` — this is your `uploadId` for the next step.
## Step 2 — Create an ExpenseReceipt
Next, create an `ExpenseReceipt` resource by calling `POST /api/v3/expenseReceipts`. This step links the uploaded file to the Expenses module:
```http Create an ExpenseReceipt theme={null}
POST /api/v3/expenseReceipts HTTP/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"ownerId": 123,
"requiresOcr": false,
"sourceId": "WebForm",
"uploadId": "a24f4279-6bb4-4e1c-9b40-a80a5d44b36d"
}
```
```json Response (200 OK) theme={null}
{
"id": "f0d01fd0-5f27-4061-8dca-bee6318b0103",
"originalReceipt": {
"id": "cb0aae89-56c9-48db-9052-48981660e0f7",
"name": "receipt-f0d01fd0-5f27-4061-8dca-bee6318b0103.pdf",
"href": "https://example.ilucca.net/api/v3/ExpenseReceipts/f0d01fd0-5f27-4061-8dca-bee6318b0103/originalReceiptFile",
"extension": ".pdf"
}
}
```
| Property | Type | Description |
| :------------ | :-------------- | :----------------------------------------------------------------------------------------------------------------------- |
| `ownerId` | `integer` | The user ID of the expense owner. Must match the owner of the expense you will create at the next step. |
| `requiresOcr` | `boolean` | Set to `true` if you want Lucca to run OCR (optical character recognition) on the receipt to automatically extract data. |
| `sourceId` | `string` | The source of the receipt. Use `"WebForm"` when creating via API. |
| `uploadId` | `string (uuid)` | The `id` returned by `/lucca-files/api/uploads` in step 1. |
Save the returned `id` (here `f0d01fd0-...`) — this is the **ExpenseReceipt ID** you will reference in the expense.
## Step 3 — Create the expense with the receipt attached
Finally, create the expense via `POST /api/v3/expenseTempItems`, referencing the `ExpenseReceipt` by its `id`:
```http Create expense with receipt theme={null}
POST /api/v3/expenseTempItems HTTP/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"expenseNatureId": 1,
"purchasedOn": "2025-06-15",
"paymentMethodId": 0,
"quantity": 1,
"originalTransaction": {
"currencyId": "EUR",
"grossAmount": 42.50
},
"processedAmounts": {
"grossAmount": 42.50,
"currencyId": "EUR"
},
"deviceId": "Web",
"ownerId": 123,
"merchant": "Restaurant ABC",
"comment": "Business lunch",
"expenseReceipts": [
{ "id": "f0d01fd0-5f27-4061-8dca-bee6318b0103" }
]
}
```
**Do not** pass the file upload ID (from step 1) directly in `expenseReceipts`. You must create an `ExpenseReceipt` resource first (step 2) and reference *that* ID. Passing the upload ID directly will result in a `403 Forbidden` error with the message: *"Property Id of type CleemyFile is not writable"*.
## Summary
```mermaid theme={null}
sequenceDiagram
participant Client
participant Files as /lucca-files/api/uploads
participant Receipts as /api/v3/expenseReceipts
participant Expenses as /api/v3/expenseTempItems
Client->>Files: POST (multipart file)
Files-->>Client: uploadId
Client->>Receipts: POST { ownerId, uploadId, ... }
Receipts-->>Client: ExpenseReceipt (id)
Client->>Expenses: POST { ..., expenseReceipts: [{ id }] }
Expenses-->>Client: ExpenseTempItem created
```
# The booked-document resource
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/booked-documents/booked-document
lucca-invoices
# List booked-documents
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/booked-documents/get-booked-documents
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/api/booked-documents
List booked-documents (i.e. invoices and credit notes).
# The cost-center resource
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/cost-centers/cost-center
lucca-invoices
# Get Cost-Center
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/cost-centers/get-cost-center
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/api/costcenters/{id}
Retrieve a cost-center by its ID.
# List Cost-Centers
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/cost-centers/get-cost-centers
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/api/costcenters
List cost-centers.
# Import Cost-Centers
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/cost-centers/import-cost-centers
/openapi-specs/lucca-invoices.yaml post /cleemy-procurement/services/imports/costcenters
Import a list of cost-centers. Endpoint to use for bulk import of cost-centers.
You may want to refer to the [dedicated guide](/api-reference/legacy/cleemy-invoices/guides/import-cost-centers).
# Create a Cost-Center
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/cost-centers/post-cost-center
/openapi-specs/lucca-invoices.yaml post /cleemy-procurement/api/costcenters
Create a new cost-center.
# Update Cost-Center
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/cost-centers/put-cost-center
/openapi-specs/lucca-invoices.yaml put /cleemy-procurement/api/costcenters/{id}
Update a cost-center.
# The inbox-document resource
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/inbox/inbox-document
lucca-invoices
# Get Invoice
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/invoices/get-invoice
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/api/invoices/{id}
Retrieve an invoice by its ID.
# List Invoice Confirmations
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/invoices/get-invoice-confirmations
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/api/invoices/{id}/confirmations
List confirmations for an invoice.
# The invoice resource
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/invoices/invoice
lucca-invoices
# Confirm an Invoice
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/invoices/post-invoice-confirmation
/openapi-specs/lucca-invoices.yaml post /cleemy-procurement/api/invoices/{id}/confirmations
Confirm (approve) an invoice.
# Update Invoice
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/invoices/put-invoice
/openapi-specs/lucca-invoices.yaml put /cleemy-procurement/api/invoices/{id}
Update an invoice.
# Get Nature
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/natures/get-nature
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/api/natures/{id}
Retrieve a nature by its ID.
# List Natures
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/natures/get-natures
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/api/natures
List natures.
# The nature resource
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/natures/nature
lucca-invoices
# Create a Nature
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/natures/post-nature
/openapi-specs/lucca-invoices.yaml post /cleemy-procurement/api/natures
Create a new nature.
# Update Nature
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/natures/put-nature
/openapi-specs/lucca-invoices.yaml put /cleemy-procurement/api/natures/{id}
Update a nature.
# The commitment resource
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/purchases/commitment
lucca-invoices
# Get Purchase
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/purchases/get-purchase
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/api/purchases/{id}
Retrieve a purchase by its ID.
# List Purchases
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/purchases/get-purchases
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/api/purchases
List purchases.
# Create a Purchase
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/purchases/post-purchase
/openapi-specs/lucca-invoices.yaml post /cleemy-procurement/api/purchases
Create a Purchase.
# Close a purchase
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/purchases/post-purchase-closing
/openapi-specs/lucca-invoices.yaml post /cleemy-procurement/api/purchases/{id}/closing
Close a purchase
# Create a commitment
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/purchases/post-purchase-commitment
/openapi-specs/lucca-invoices.yaml post /cleemy-procurement/api/purchases/{id}/commitment
Create a commitment.
# The purchase resource
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/purchases/purchase
lucca-invoices
# Get Supplier
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/suppliers/get-supplier
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/api/suppliers/{id}
Retrieve a supplier by its ID.
# List Suppliers
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/suppliers/get-suppliers
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/api/suppliers
List suppliers.
# Create a Supplier
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/suppliers/post-supplier
/openapi-specs/lucca-invoices.yaml post /cleemy-procurement/api/suppliers
Create a new supplier.
# Update Supplier
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/suppliers/put-supplier
/openapi-specs/lucca-invoices.yaml put /cleemy-procurement/api/suppliers/{id}
Update a supplier.
# The supplier resource
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/suppliers/supplier
lucca-invoices
# Create a commitment request
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/workflow/post-commitment-request
/openapi-specs/lucca-invoices.yaml post /cleemy-procurement/api/commitments/{id}/requests
Trigger the approval workflow for a purchase commitment. Request body is expected to be empty.
# Cancel a commitment-request
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/workflow/post-commitment-request-cancellation
/openapi-specs/lucca-invoices.yaml post /cleemy-procurement/api/requests/{id}/cancellations
Cancel a commitment-request.
# Create a new User
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/create-a-new-user
/openapi-specs/directory-v3.yaml post /api/v3/users
Create a new user
# Delete a work-contract
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/delete-work-contract
/openapi-specs/directory-v3.yaml delete /directory/api/4.0/work-contracts/{id}
Delete a work-contract by its ID.
You cannot delete a work-contract if it is the only one an employee has.
# Get a User by Id
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/get-a-user-by-id
/openapi-specs/directory-v3.yaml get /api/v3/users/{id}
Retrieve a single User identified by its unique identifier.
This legacy endpoint has its equivalent in the **Lucca API** (currently in beta): [`GET /lucca-api/employees/{id}`](/api-reference/latest/get-employee).
To retrieve all data for a given employee (including extensions) in a single call, you may also use [`GET /lucca-api/employee-attributes`](/api-reference/latest/get-employee-attributes) with the `employee.id` filter.
# Retrieve a work-contract
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/get-work-contract
/openapi-specs/directory-v3.yaml get /directory/api/4.0/work-contracts/{id}
Retrieve a single work-contract by its ID.
This legacy endpoint has its equivalent in the **Lucca API** (currently in beta): [`GET /lucca-api/employments/{id}`](/api-reference/latest/get-employment).
Career data (department, manager, job title) is now tracked separately through the [job-position](/api-reference/latest/employments/job-position) resource.
# List work-contracts
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/get-work-contracts
/openapi-specs/directory-v3.yaml get /directory/api/4.0/work-contracts
Retrieve a paginated collection of work-contracts.
This legacy endpoint has its equivalent in the **Lucca API** (currently in beta): [`GET /lucca-api/employments`](/api-reference/latest/get-employments).
Career data (department, manager, job title) is now tracked separately through the [job-position](/api-reference/latest/employments/job-position) resource.
# Delegation
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/guides/delegation
## Introduction
Delegation consists of choosing a someone to delegate to and the delegation period :
* only during manager's holidays, this is the default strategy
* permanently, if I never have time to validate the requests of my collaborators
* from date to date, for a one-off delegation not related to my holidays
Here are the properties of the delegation resource:
* Owner: the one who delegates
* Delegatee: the user targeted by the delegation, the one to whom we delegate
* StartsOn: for a date-to-date delegation, the start date (included)
* EndsOn: for a date-to-date delegation, the end date (inclusive)
* OnlyDuringHolidays: Boolean that allows you to indicate that you only want to delegate during your holidays & absences
* IsActive: indicates the state of the delegation, allows if necessary to temporarily deactivate the delegation without losing its information (delegate, strategy)
NB: The delegation does not have a unique ID, so it is a resource available under the 'users' resource.
### Retrieve the delegation of the current user
You can retrieve delegation information via the following API.
```http theme={null}
GET api/v3/users/me/delegation
```
```json theme={null}
{
"delegateeId": 33,
"delegatee": {
"id": 33,
"name": "John Doe"
},
"startsOn": null,
"endsOn": null,
"isActive": true,
"onlyDuringHolidays": true
}
```
### Modify the user's delegate
Here we assign user (id=34) as the delegate of user (id=123) with the same previous period of delegation.
```http theme={null}
PUT /api/v3/users/123/delegation HTTPS/2
Host: example.ilucca.net
Content-Type: application/json
{
"delegatee": {
"id": 34
}
}
```
### Modify delegation period
In order to swith the period from "during holidays" to a date to date period, you need to deactivate the first strategy and indicate the period.
```http theme={null}
PUT /api/v3/users/123/delegation HTTPS/2
Host: example.ilucca.net
Content-Type: application/json
{
"onlyDuringHolidays": false,
"startsOn": "2016-01-01",
"endsOn": "2016-12-31"
}
```
For a permanent delegation, set `startsOn` and `endsOn` to `NULL`. You can also set only the startsDate so that the permanent delegation strarts from a specific date.
### Deactivate delegation
To cancel or deactivate temporarily the delegation, change the activation status.
```http theme={null}
PUT /api/v3/users/123/delegation HTTPS/2
Host: example.ilucca.net
Content-Type: application/json
{
"isActive": false
}
```
Do the opposite to reactivate it.
# Extended Data
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/guides/extended-data
ExtendedData are custom employee extension properties created
through the HR File configuration interface.
It makes it possible to add custom data to your employees. First,
by defining them through `extensionUserDefinitions` objects, then
by setting their value through `extendedData` objects.
## Introduction
ExtendedData values can be retrieved through the users API endpoint.
They are listed as properties of a JSON object named `extendedData`.
Such extended data can be of different types and formats (refer to the
[UserProperties](./user-properties) documentation). Here is an example
with various format values.
```json Simple Example theme={null}
GET /api/v3/users?fields=firstName,extendedData HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
{
"firstName": "Paul",
"extendedData": {
"e_secondName": {
"id": 4547,
"value": "André"
}
}
}
```
```json Various Types Example theme={null}
GET /api/v3/users?fields=firstName,extendedData HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
{
"id": 476,
"extendedData": {
// Single-value (per employee) of type "text"
"e_github_login": { "id": 15847, "value": "shipit" },
// Single-value (per employee) of type "list"
"e_eyes_color": { "id": 8976, "value": 84 },
// Single-value (per employee) of type "date"
"e_diploma_date": { "id": 16952, "value": "2016-09-01T00:00:00" },
// Multiple-value of type "text"
"e_favorite_food": [
{ "id": 4589, "value": "beer" },
{ "id": 4590, "value": "free beer" }
],
// Single-value (per employee) of type "composite"
"e_significant_other": {
"id": 2635,
"value": {
"e_so_firstname": { "id": 2636, "value": "Lola" },
"e_so_lastname": { "id": 2637, "value": "Bunny" }
}
},
// Multiple-value of type "composite"
"e_objectives": [
{
"id": 1726,
"value": {
// Child value - number
"e_objective_year": { "id": 15855, "value": 2017 },
// Child value - text
"e_objective_description": { "id": 15856, "value": "Write more doc" }
}
},
{
"id": 1727,
"value": {
"e_objective_year": { "id": 15865, "value": 2018 },
"e_objective_description": { "id": 15867, "value": "More doc!" }
}
}
],
// ExtendedData with no value set
"e_number_of_children": null,
// ExtendedData with a value which has been deleted
"e_quote": { "id": 4896, "value": null }
}
}
```
## Writing on ExtendedData Values
All example payloads described in this section are sent to the `/api/v3/users/{id}`
API endpoint (`{id}` being the ID of the user that should be updated).
Extended data values are READ through the optional `extendedData` field on the User.
But they are WRITTEN through passing their values at the root level of the User object.
```http Write Request theme={null}
PUT /api/v3/users/416?fields=lastName,extendedData HTTPS/2
Host: example.ilucca.net
Authorization: lucca application=XXX
> Request
Accept: application/json
Accept-Encoding: br, gzip
Content-Type: application/json
{
"lastName": "Doe",
"e_quote": { "id": 4896, "value": "test" }
}
< Response
200 OK
Content-Type: application/json
{
"lastName": "Doe",
"extendedData": {
"e_quote": { "id": 4896, "value": "test" }
}
}
```
Attempting to write on extended-data values through the `extendedData` User
property will NOT work.
### Simple Single-Value ExtendedData
A "simple" ExtendedData is an extension property that only has one property itself.A "single-value" ExtendedData is an extension property that does not accept more than one
value for each employee.
To update an ExtendedData value that does not exist, you must not specify its Id
(or set it to 0).
```json JSON Example 5 theme={null}
PUT /api/v3/users/416 HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"e_quote": {
"value": "99 little bugs in the code..."
}
}
```
To update an ExtendedData value that already exists, you must specify the ID of the
ExtendedData value.
```json HTTP Request theme={null}
PUT /api/v3/users/416 HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"e_quote": {
"id": 4896,
"value": "Take one down, patch it around. 127 little bugs in the code..."
}
}
```
To delete the value from an ExtendedData, simply set its `value` property to `null`.
The ExtendedData value ID must be specified.
```json HTTP Request theme={null}
PUT /api/v3/users/416 HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"e_quote": {
"id": 4896,
"value": null
}
}
```
### Simple Multiple-Value ExtendedData
A "simple" ExtendedData is an extension property that only has one property itself.A "multiple-value" ExtendedData is an extension property that accepts more than one
value for each employee.
To create a multiple-value ExtendedData value, you must send all the occurrences
at the same time and not specify the ID of these occurrences (or set them to 0).
```json HTTP Request theme={null}
PUT /api/v3/users/416 HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"e_favorite_food": [
{ "id": 0, "value": "beer" },
{ "id": 0, "value": "free beer" }
]
}
```
To add occurrences to a multi-valued ExtendedData value that already exists, all
occurrences must be sent at the same time without specifying the IDs of the new
occurrences (or setting them to 0).
```json HTTP Request theme={null}
PUT /api/v3/users/416 HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"e_favorite_food": [
{ "id": 4589, "value": "beer" },
{ "id": 4590, "value": "free beer" },
// New occurrences
{ "id": 0, "value": "cold beer" },
{ "id": 0, "value": "more beer" },
]
}
```
In order to delete some values of an existing multiple-values ExtendedData,
you just have to send only the values you want to keep.
Note: sending an empty array results in the deletion of all existing values
for this ExtendedData (and employee).
```json HTTP Request theme={null}
PUT /api/v3/users/416 HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"e_favorite_food": [
{ "id": 4589, "value": "beer" }
// Other existing values will be deleted
]
}
```
### Composite Single-Value extendedData
A "composite" ExtendedData is an extension property that has more than one property itself.A "single-value" ExtendedData is an extension property that does not accept more than one
value for each employee.
In order to create a value for an existing composite single-value ExtendedData, send a JSON object
whose key is the name of the ExtendedData, and whose value is a JSON object with a `value` property
which in itself is a JSON object setting the value of each of the ExtendedData properties.
When creating a value, the ExtendedData `id` may be either omitted or set to zero `0`.
```json HTTP Request theme={null}
PUT /api/v3/users/416 HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"e_significant_other": {
"id": 0,
"value": {
"e_so_firstname": {
"id": 0,
"value": "Miss"
},
"e_so_lastname": {
"id": 0,
"value": "Piggy"
}
}
}
}
```
In order to update a composite ExtendedData value, send ALL of its properties values
along their own IDs.
```json HTTP Request theme={null}
PUT /api/v3/users/416 HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"e_significant_other": {
"id": 2635,
"value": {
"e_so_firstname": {
"id": 2636,
"value": "Lola"
},
"e_so_lastname": {
"id": 2637,
"value": "Bunny"
}
}
}
}
```
In order to delete a composite ExtendedData value, send all the properties of the composite
ExtendedData along their IDs with their values set to null.
```json HTTP Request theme={null}
PUT /api/v3/users/416 HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"e_significant_other": {
"id": 2635,
"value": {
"e_so_firstname": {
"id": 2636,
"value": null
},
"e_so_lastname": {
"id": 2637,
"value": null
}
}
}
}
```
### Composite Multiple-Value ExtendedData
A "composite" ExtendedData is an extension property that has more than one property itself.A "multiple-value" ExtendedData is an extension property that accepts more than one
value for each employee.
In order to create a multiple-value composite ExtendedData, send all the values
at the same time while not givin IDs to the new ones (or set them to 0).
```json HTTP Request theme={null}
PUT /api/v3/users/416 HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"e_objectives": [
// First occurrence
{
"id": 0,
"value": {
"e_e_objective_description": { "id": 0, "value": "Write more doc" },
"e_objective_year": { "id": 0, "value": 2017 }
}
}
]
}
```
In order to delete some or all values of a multiple-value composite extendedData,
only send the values to keep. To remove all values, send an empty array.
```json HTTP Request theme={null}
PUT /api/v3/users/416 HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"e_objectives": [
{
"id": 1726,
"value": {
"e_e_objective_description": { "id": 15855, "value": "Write more doc" },
"e_objective_year": { "id": 15856, "value": 2017 }
}
},
{
"id": 0,
"value": {
"e_objective_year": { "id": 0, "value": 2018 },
"e_e_objective_description": { "id": 0, "value": "More doc!" }
}
}
]
}
```
## FAQ
### Retrieve entries from a list type ExtendedData
[The UserProperties API](./user-properties) can be used to retrieve possible entries from a list of values. However, the ExtensionUserDefinitions API `/api/v3/extensionuserdefinitions` allows to retrieve more information about the possible entries for list type ExtendedData. The objects returned by this API contain an `extensionUserPropertyListEntries` property, which is an array containing useful information about the entries in the list:
* `id`: numeric value identifying the list entry, which can be inserted into ExtendedData values via the users API;
* `name`: lthe text associated with the list entry, translated into the language of the current user;
* `translation`: the text associated with the list entry, translated into all available languages:
* `culturedLabels`
* `culture.code`: the language of the translation ("en-US", "fr-FR", "es-ES", etc)
* `value`: the translated text in the specified language
```json JSON Example 17 theme={null}
// GET xxx.ilucca.net/api/v3/extensionuserdefinitions?fields=id,formatKey,extensionUserPropertyListEntries[id,name,translation[culturedLabels[culture.code,value]]]
items: [
{
"id": "e_eyes_color",
"formatKey": "select",
"extensionUserPropertyListEntries": [
{
"id": 84,
"name": "Brown",
"translation": {
"culturedLabels": [
{ "culture": { "code": "en-US" }, "value": "Brown" },
{ "culture": { "code": "fr-FR" }, "value": "Marron" },
]
}
},
{
"id": 85,
"name": "Blue",
"translation": {
"culturedLabels": [
{ "culture": { "code": "en-US" }, "value": "Blue" },
{ "culture": { "code": "fr-FR" }, "value": "Bleu" },
]
}
},
{
"id": 86,
"name": "Green",
"translation": {
"culturedLabels": [
{ "culture": { "code": "en-US" }, "value": "Green" },
{ "culture": { "code": "fr-FR" }, "value": "Vert" },
]
}
}
]
},
{
"id": "e_quote",
// e_quote is not a list, so extensionUserPropertyListEntries is empty
"formatKey": "short",
"extensionUserPropertyListEntries": [],
},
...
]
```
# Using the user-properties API
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/guides/user-properties
The UserProperties API can be used to retrieve information on the structure of all the properties that can be attached to a user. This API is read-only: only the HTTP `GET` request method is therefore available on this API.
The `UserProperties` API can be used to retrieve information on the structure of all the properties that can be attached to a user. This API is read-only: only the HTTP `GET` request method is therefore available on this API.
## UserProperty structure
```json theme={null}
{
"id": "codeIdentifiantLaPropriete",
"name": "Property name in logged-in user culture",
"isMultivalue": false,
"isNullable": false,
"isReadOnly": true,
"category": 0, // see categories section
"type": 4, // see "Property types and formats" section
"format": {
"key": "codeIdentifiantLeFormat"
// The object sometimes contains other properties, see the section "Property types and formats"
}
}
```
## Property types and formats
### Type
The `type` property of a UserProperty is an enumeration allowing to know the type of data that it is possible to store within a user property.
* `0` : Boolean ;
* `1` : Integer or list of values ;
* `2` : Numerical ;
* `3` : Date ;
* `4` : Texte ;
* `5` : Objet. Identifies UserProperties containing a value that is not simple, such as another user, an image, or a file. The `format` property described in the section below provides additional information on the type of data.
### Format
The `format` property of a UserProperty designates an object containing various information about how to display and edit a user property.
#### Format of boolean properties
Boolean type properties always have the following format:
```json theme={null}
{ "key": "boolean" }
```
#### Integer Value and Value List Property Formats
Integer value type properties always have the following format:
```json theme={null}
{ "key": "integer" }
```
Properties of type integer can also designate properties of type list of values, to which their format has the key `select`, or `radio` in the case of lists containing only two possible values. The following formats are possible in this case:
* If the property belongs to the native category:
```json theme={null}
{
"key": "select",
"entries": [ // Table containing the possible entries for the list of values
{
"key": "Female", // Unique character string for this property and identifying the list entry
"description": "Femme", // Name of the entry translated into the language of the logged in user
"descriptions": { // Translations of the entry in the different languages available
"fr-FR": "Femme",
"en-US": "Female"
}
},
{
"key": "Undefined",
"description": "Non défini",
"descriptions": {
"fr-FR": "Non défini",
"en-US": "Undefined"
}
},
{
"key": "Male",
"description": "Homme",
"descriptions": {
"fr-FR": "Homme",
"en-US": "Male"
}
}
],
}
```
* If the property belongs to the extension category:
```json theme={null}
{
"key": "select",
"entries": [ // Table containing the possible entries for the list of values
{
"key": 48, // Integer value identifying the list entry
"description": "Vide", // NName of the entry translated into the language of the logged in user
"descriptions": { // Translations of the entry in the different languages available
"fr-FR": "Vide",
"en-US": "Empty"
}
},
{
"key": 49,
"description": "Valeur 1",
"descriptions": {
"fr-FR": "Valeur 1",
"en-US": "Value 1"
}
},
{
"key": 50,
"description": "Value 2",
"descriptions": {
"fr-FR": "Valeur 2",
"en-US": "Value 2"
}
}
]
}
```
#### Formats of numeric value type properties
Numeric value type properties can have one of the following formats:
```json theme={null}
{ "key": "numeric" },
{ "key": "money" }
```
#### Format of date-type properties
Date type properties always have the following format:
```json theme={null}
{ "key": "date" }
```
#### Formats of text properties
Text type properties can have one of the following formats:
```json theme={null}
{ "key": "short", "maximumLength": 255 },
{ "key": "superShort", "maximumLength": 50 },
{ "key": "multi", "maximumLength": 4000 },
{ "key": "url", "maximumLength": 4000 },
{ "key": "iban", "maximumLength": 255 },
{ "key": "bic", "maximumLength": 255 },
```
#### Object Type Property Formats
Some properties contain a value retrieved via another Lucca API, in this case their format is as follows:
```json theme={null}
{
"key": "department" | "user" | "scopeFiltered" | "culture" | "lucca_api_select" | "workcycle",
// API URL to contact to retrieve possible values
"url": "/api/v3/departments",
// Filter to use during the API call to retrieve the correct values
"filter": "isActive=true"
}
```
File and image type user properties contain a GUID that identifies a file stored on the Lucca server. Example of value:
```json theme={null}
{
"id": "3411dc37-0242-48ea-9ef7-33a6ec05b4e1",
"name": "file-name.pdf",
"href": "https://xxx.local.dev/getFile.ashx?id=3411dc37-0242-48ea-9ef7-33a6ec05b4e1"
}
```
The properties identifying an image or a file have the following formats respectively:
```json theme={null}
{ "format": "picture" },
{ "format": "file" }
```
## Categories
There are different types of `UserProperties`. The `category` property of each `UserProperties` object lets you know to which category a `UserProperties` belongs.
* `0` : Native property ;
* `1` : Attribute property ;
* `2` : Applicative property ;
* `3` : Extension property (extendedData) ;
* `4` : Analytical axes property ;
* `5` : Password property.
### 0 : Native property
These properties are static and necessary for the proper functioning of Lucca applications. Some of these properties are only reported by the `UserProperties` API if specific Lucca applications are instantiated.
`(~ config)` : depends on application configuration
| Name | Availability |
| :------------------------------ | :---------------------------------------------------------- |
| `address` | if **Lucca Core HR** (\~ config) |
| `allowsElectronicPayslip` | if **Lucca Payslips** |
| `bankName` | always |
| `bic` | if **Lucca Expenses** |
| `birthDate` | always |
| `calendar` | always |
| `civilTitle` | always |
| `corporateCard` | if **Lucca Expenses** |
| `csp` | if **Lucca Absences** |
| `culture` | always |
| `department` | always |
| `directLine` | always |
| `dtContractEnd` | always |
| `dtContractStart` | always |
| `employeeNumber` | always |
| `firstName` | always |
| `frenchCarTaxHorsePower` | if **Lucca Expenses** |
| `frenchMotocyclesTaxHorsePower` | if **Lucca Expenses** |
| `gender` | always |
| `habilitedRoles` | always |
| `iban` | if **Lucca Expenses** |
| `insuranceNumber` | if **Lucca Payslips** or if **Lucca Absences** (\~ config) |
| `jobTitle` | always |
| `lastName` | always |
| `legalEntity` | always |
| `login` | always |
| `mail` | always |
| `manager` | if **Lucca Absences** and/or **Lucca Expenses** (\~ config) |
| `nationality` | always |
| `personalAccount` | if **Lucca Expenses** (\~ config) |
| `personalEmail` | always |
| `personalMobile` | always |
| `picture` | always |
| `professionalMobile` | always |
| `quote` | always |
| `rolePrincipal` | always |
| `seniorityDate` | if **Lucca Absences** |
| `site` | if **Lucca Absences** |
| `unitSellPrice` | if **Lucca Absences** and/or **Lucca Compensation** |
| `userWorkCycles` | if **Lucca Timesheets** and/or if **Lucca Absences** |
### 1 : Attribute properties
Attribute category properties are custom properties that were created through the custom fields admin (`xxx.ilucca.net/admin/extended-datas`). All of these properties are of type text and have a maximum length of 255 characters.
### 2 : Applicative properties
Application properties are specific to a specific Lucca application. The `UserProperties` API will only report these properties if the applications that use them are instantiated. In some cases, certain properties are only available if the relevant application has been configured to use them.
* Example for Lucca Absences : `profile_figgo` ; `manager2`.
* Example for Lucca expenses : `profile_cleemy`.
### 3 : Extension properties (extendedData)
Properties in the extension category, called **ExtendedData**, are custom properties that were created through the extensions admin (`xxx.ilucca.net/directory/settings#/hrfile/properties`) . These properties are basically similar to attribute type properties, but are more comprehensive and can have different types. These properties are only available if the HR Folder is instantiated. More information on the [ExtendedData](./extended-data) documentation page.
### 4 : Analytical axes properties
Properties in the Analytical Axes category are properties that have been created through the Analytical Axes admin (`xxx.ilucca.net/admin/axes`). These properties behave similarly to properties containing a value retrieved via another Lucca API. See the [Formats of object type properties](#formats-des-propetes-de-type-o-...) section.
### 5 : Password properties
This category of properties is only reserved for the `password` property.
# Users Examples
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/guides/users
Here are a few example requests to the users API:
### Get all currently active users
`GET /api/v3/users` retrieves all active users from the database.
### Get all users, terminated or not
GET `/api/v3/users?dtContractEnd=until,2015-10-15,null` retrieves all users, terminated or not (it
actually retrieves all departed users up to Octobre, 15th 2015 as well as all active and future users).
**The date should be replaced with today's.**
### Get a single user
`GET /api/v3/users/{id}` retrieves all data about the user that has the `{id}` id.
`GET /api/v3/users?firstName=John` retrieves all users called 'John'.
`GET /api/v3/users?firstName=starts,Jo` retrieves all users whose first name starts with 'Jo'.
`GET /api/v3/users?matricule=001` retrieves the user that has the '001' matricule (employee number, unique to each employee).
### Get specific user properties
You can specify which properties you need throught the `fields` parameter.
`GET /api/v3/users?fields=firstName,lastName,birthDate` retrieves the first name, last
name and birth date of all active users.
### Update a user's last name
`PUT /api/v3/users/1`
```json theme={null}
{
"lastName": "Smith"
}
```
# V3 Users to V5 Employees
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/guides/users-migration
Migration steps from the legacy /api/v3/users endpoint to the Lucca API employee resources.
The v3 users API is a monolithic, read/write endpoint that exposes the current state of employees as a flat JSON object. It is now deprecated in favour of the **Lucca API** (v5), which introduces a richer, historized model split across several dedicated resources.
**As of today, this migration is only applicable to read-only integration scenarios.** The Lucca API employee resources (`/lucca-api/employees`, `/lucca-api/employments`, `/lucca-api/job-positions`, and `/lucca-api/employee-attributes`) are currently **read-only**. If your integration writes to `/api/v3/users` (creates, updates, or deletes users), it cannot be migrated to the Lucca API at this time and you should keep using the v3 endpoint until a write API becomes available.
No sunset date has been announced for `/api/v3/users` yet. Make sure **your API key has a valid contact email address** so you receive deprecation notices when a date is set.
If your integration uses `/directory/api/4.0/work-contracts`, see the dedicated [work-contracts migration guide](/api-reference/legacy/directory/guides/work-contracts-migration).
## Quick Start (TL;DR)
Replace your API key header (`Authorization: lucca application=...`) with an OAuth 2.0 bearer token. Request the scopes you need (e.g. `employees.readonly`, `employee-attributes.readonly`).
Include `Api-Version: 2024-11-01` in every request.
`GET /lucca-api/employees?status=active,upcoming` returns the same set of active employees.
Instead of `?fields=...`, query `GET /lucca-api/employee-attributes?definition.id=employment.start,jobPosition.department,...&applicability.asOf={TODAY}` to retrieve any property in a single call. See the [Field Mapping Reference](#field-mapping-reference) for the full translation table.
***
## What Changes
A single endpoint returning a flat representation of the **current** state of an employee, combining identity, contract, position, personal data, and application-specific fields.
The employee model is split across several dedicated, historized resources. A unified read endpoint (`/lucca-api/employee-attributes`) lets you query any property in a consistent way.
The key differences are:
* **Authentication**: v3 uses a proprietary API key scheme; the Lucca API uses **OAuth 2.0 bearer tokens**. Refer to the [authentication guide](/documentation/using-api/authentication) for details.
* **Versioning**: requests to the Lucca API must include the `Api-Version: 2024-11-01` HTTP header.
* **Historized model**: rather than a snapshot of the current state, the Lucca API tracks the full career history of an employee (multiple employments, multiple job-positions over time).
* **Field selection**: the v3 `?fields=` mechanism is replaced either by querying the relevant sub-resource directly, or by using the `employee-attributes` endpoint with a `definition.id` filter.
* **Pagination**: collection endpoints use cursor-based pagination (`?include=links&limit={page_size}&page={page_token}`). Max **1000 items per page** (default 25). See the [pagination guide](/documentation/using-api/paging) and [limits documentation](/documentation/limits) below.
Learn how to paginate through collection endpoints in the Lucca API.
Learn about the rate and size limits applied to the Lucca API and best practices to design your integration accordingly.
***
## First: Switch to OAuth 2.0 and Set the Api-Version Header
First of all, replace your API key header with an OAuth 2.0 bearer token obtained from the Lucca authorization server. You will need to request
the appropriate scopes for the resources you intend to read:
| Resource | Required scope |
| :---------------------------------- | :---------------------------------------- |
| `employee` | `employees.readonly` |
| `employee-personal-record` | `employee-personal-records.readonly` |
| `employment` | `employments.readonly` |
| `job-position` | `job-positions.readonly` |
| `employee-attributes` (values) | `employee-attributes.readonly` |
| `employee-attributes` (definitions) | `employee-attribute-definitions.readonly` |
The `employee-attribute-definitions.readonly` scope is only needed if you want to **discover** attribute IDs programmatically (e.g. to enumerate
all custom fields via `GET /lucca-api/employee-attribute-definitions`). If your integration already knows the definition IDs it needs,
`employee-attributes.readonly` is sufficient.
A single OAuth 2.0 token can carry multiple scopes. When requesting your token, combine all the scopes you need in one request — for example:
`scope=employees.readonly employee-attributes.readonly employee-personal-records.readonly`. You do not need a separate token per resource.
Additionally, the v5 Lucca API is versioned, so you must include the `Api-Version: {VERSION}` HTTP header in all your requests. Currently, the
latest version is `2024-11-01`.
Read more about how to obtain and use OAuth 2.0 bearer tokens.
Read more about how to use the `Api-Version` header to specify the API version.
***
## The New Data Model
The legacy `user` object is split across the following Lucca API resources:
The [`employee`](/api-reference/latest/employees/employee) resource holds the public-facing identity of an employee: name,
email, employee number, profile picture, and status. It also exposes `applicableJobPosition` and `applicableEmployment` references,
which point to the employee's current position and contract respectively.
The [`employee-personal-record`](/api-reference/latest/employees/employee-personal-record) resource holds private personal data:
birth date, personal email, phone number, bank account details, nationality, gender, and more. Access requires appropriate
OAuth scopes and hr file section permissions.
The [`employment`](/api-reference/latest/employments/employment) resource represents the contractual relationship between an employee
and a legal entity. It carries the contract start and end dates, and replaces the legacy `dtContractStart` / `dtContractEnd` fields.
The [`job-position`](/api-reference/latest/employments/job-position) resource represents a position occupied by an employee during
an employment: department, manager, business establishment, job title, job qualification, occupation category, and more. An employee
may hold several successive job positions over the course of a single employment.
The [`employee-attributes`](/api-reference/latest/employee-attributes/get-started) endpoint surfaces any property from any of
the above resources — including custom extension fields — in a single, uniform query. This is the recommended starting point for
read-only integrations that previously used `GET /api/v3/users?fields=...`.
Use the [Field Mapping Reference](#field-mapping-reference) below to translate every legacy v3 field name to its new `employee-attribute`
definition ID. Before doing so, review the [notable breaking changes](#notable-breaking-changes) — several field names are semantically
ambiguous or changed type in ways the table alone won't make obvious.
***
## Migration by Use Case
### 1. Retrieve Users
The v3 `GET /api/v3/users` endpoint returns a list of all active and upcoming employees. It automatically excludes former employees.
In the Lucca API, use `GET /lucca-api/employees?status=active,upcoming` to retrieve the same list of active employees. To include
former employees (i.e. "deactivated") as well, then omit the `status` filter.
For reference, the employee (v5) status can be:
* `active`: the employee has an active employment (current date is between the start and end dates of one of their employments);
* `upcoming`: the employee has no active employment, but at least one future employment (start date is in the future);
* `deactivated`: the employee has no active nor future employment (all employments ended in the past).
```http V3 theme={null}
GET /api/v3/users?fields=id,firstName,lastName,mail,dtContractStart,dtContractEnd HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
```
```http V5 theme={null}
GET /lucca-api/employees?status=active,upcoming&limit=100&include=links HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"type": "employees",
"url": "...",
"items": [
{
"id": "416",
"type": "employee",
"url": "https://example.ilucca.net/lucca-api/employees/416",
"givenName": "John",
"familyName": "Doe",
"email": "john.doe@example.com",
"employeeNumber": "EMP-0416",
"status": "active",
"applicableEmployment": { "id": "4561", "type": "employment", "url": "..." },
"applicableJobPosition": { "id": "74", "type": "job-position", "url": "..." }
}
],
"links": {
"prev": null,
"next": { "href": "https://example.ilucca.net/lucca-api/employees?status=active,upcoming&limit=100&page=!4sKv" }
}
}
```
The v3 `?fields=` mechanism is not available in the Lucca API. To retrieve specific fields, either query the relevant sub-resource directly
(e.g. `GET /lucca-api/employee-personal-records/{employeeId}` to get personal data), or use the `employee-attributes` endpoint (more below)
with a `definition.id` filter to retrieve any property in a single call.
When fetching a single user by ID, the v3 endpoint is `GET /api/v3/users/{id}`. In the Lucca API, use `GET /lucca-api/employees/{id}`.
The v5 `employee.id` value is the same as the v3 `user.id`, though note that v5 represents IDs as strings (`"416"`) rather than integers (`416`) in JSON.
```http V3 theme={null}
GET /api/v3/users/416?fields=id,firstName,lastName,mail,dtContractStart,dtContractEnd HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
{ "id": 416, ... }
```
```http V5 theme={null}
GET /lucca-api/employees/416 HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{ "id": "416", ... }
```
### 2. Retrieve the Current Employment and Job Position
In v3, contract dates and position data were flat properties of the user object. In the Lucca API, new API resources appear: `employment`
and `job-position`.
To retrieve the current employment and job position, use the `applicableJobPosition` and `applicableEmployment` references in the employee
response. You may either follow their URLs to get the full details, or use the `?include=embedded` mechanism to inline them directly
in the employee response. Lastly, you can also retrieve them via the `employee-attributes` endpoint, which is the recommended approach
for bulk reads.
The `?include=` query parameter accepts a comma-separated list of options:
* `embedded` — inlines the full representation of referenced resources directly in the response;
* `links` — adds pagination links (`links.next`, `links.prev`) and relation links to the response;
* `totalCount` — includes the total count of items across all pages.
You can combine them: `?include=embedded,links`.
#### 2.1. Via the `employee` Endpoint (embedded references)
Add `?include=embedded` to the GET employee request. This tells the server to inline the full representation of the referenced `employment`
and `job-position` resources directly in the response, so you do not need to make additional requests to follow their URLs.
```http v3 theme={null}
GET /api/v3/users/416?fields=id,dtContractStart,dtContractEnd,department[id,name] HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
{
"id": 416,
"dtContractStart": "2020-01-15",
"dtContractEnd": null,
"department": {
"id": 5,
"name": "Sales"
},
...
}
```
```http v5 theme={null}
GET /lucca-api/employees/416?include=embedded HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"id": "416",
...,
"applicableEmployment": { "id": "12", "type": "employment", "url": "..." },
"applicableJobPosition": { "id": "34", "type": "job-position", "url": "..." },
"embedded": {
"employment": {
"12": {
"id": "12",
"start": { "date": "2020-01-15" },
"end": null,
...
}
},
"job-position": {
"34": {
"id": "34",
"department": { "id": "5", "type": "department", "url": "..." },
"manager": { "id": "417", "type": "employee", "url": "..." },
...
}
},
"department": {
"5": {
"id": "5",
"name": "Sales",
...
}
}
}
}
```
Rather than embedding them, you may also follow their URLs to get full employment and job-position details.
Learn more about how to use the `?include=embedded` mechanism to inline referenced resources directly in the response.
#### 2.2. Via the `employee-attributes` Endpoint
The `employee-attributes` endpoint is the recommended way to retrieve employee data in bulk. It allows you to query any property
of an employee — including properties of related resources like `employment` and `job-position` — in a single, uniform way.
To retrieve attributes of related resources, just include their definition IDs in the `definition.id` filter.
The `applicability.asOf` parameter is required when querying time-bounded resources like `employment` or `job-position`. Without it, the endpoint
may return multiple historical values per attribute (e.g. every job position an employee ever held). Always pass today's date to retrieve each
employee's current state.
```http v5 theme={null}
GET /lucca-api/employee-attributes?employee.id={EMPLOYEE_ID}&applicability.asOf={TODAY}&definition.id=employment.start,employment.end,jobPosition.department,jobPosition.manager HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"type": "employee-attributes",
"items": [
{
"id": "34879",
"type": "employee-attribute",
"definition": { "id": "employment.start", "type": "employee-attribute-definition" },
"employee": { "id": "416", "type": "employee" },
"applicability": { "start": "2020-01-15", "end": null },
"value": { "date": "2020-01-15" }
},
{
"id": "7412",
"type": "employee-attribute",
"definition": { "id": "employment.end", "type": "employee-attribute-definition" },
"employee": { "id": "416", "type": "employee" },
"applicability": { "start": "2020-01-15", "end": null },
"value": null
},
{
"id": "51903",
"type": "employee-attribute",
"definition": { "id": "jobPosition.department", "type": "employee-attribute-definition" },
"employee": { "id": "416", "type": "employee" },
"applicability": { "start": "2022-06-01", "end": null },
"value": { "id": "5", "type": "department" }
},
{
"id": "8266",
"type": "employee-attribute",
"definition": { "id": "jobPosition.manager", "type": "employee-attribute-definition" },
"employee": { "id": "416", "type": "employee" },
"applicability": { "start": "2022-06-01", "end": null },
"value": { "id": "417", "type": "employee" }
}
],
"links": { "prev": null, "next": null }
}
```
Learn more about the `employee-attributes` endpoint, the recommended way to query employee data in bulk.
When using `employee-attributes`, `applicability.asOf={TODAY}` limits results to currently valid attributes but does not exclude
employees whose contract has ended — to filter to active employees only, first page through `GET /lucca-api/employees?status=active` to collect
the IDs, then pass batches of up to 1000 at a time as `employee.id={ID1},{ID2},...` (the `employee.id` filter accepts a maximum of 1000 values
per request).
***
### 3. Retrieve Personal Data
Personal data that was previously embedded in the flat v3 user object (birth date, bank details, personal email, etc.) is now housed in
the `employee-personal-record` resource. Accessing it requires the `employee-personal-records.readonly` OAuth scope and the appropriate
hr file section permissions.
Choose the approach that fits your use case:
* Use `GET /lucca-api/employee-personal-records/{employeeId}` when you need the **complete personal record** for a known employee — it returns
all personal fields in one response.
* Use `employee-attributes` when you only need **specific personal fields**, are reading data for many employees in bulk, or want to mix
personal data with other attributes (employment, job position, etc.) in a single call.
```http v5 theme={null}
GET /lucca-api/employee-personal-records/{employeeId} HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
```
Full reference for the `employee-personal-record` resource, including all available fields and their types.
Or via `employee-attributes`:
```http v5 theme={null}
GET /lucca-api/employee-attributes?employee.id={EMPLOYEE_ID}&applicability.asOf={TODAY}&definition.id=personal.birthDate,personal.email,personal.phoneNumber HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
```
***
## Notable Breaking Changes
Pay close attention to these semantic changes — they may cause silent data errors if overlooked.
### `legalEntity` is ambiguous
The v3 field name `legalEntity` maps to two different v5 concepts depending on context:
| v3 field | v5 definition ID | v5 concept |
| :------------------------------ | :---------------------------------- | :-------------------------------------------------------------------------- |
| `legalEntity` / `legalEntityId` | `jobPosition.businessEstablishment` | The business establishment where the employee physically works |
| `legalUnit` / `legalUnitId` | `employment.legalEntity` | The legal entity on the employment contract (called "legal-unit" in the UI) |
### `birthDate` moved
Previously a top-level user field, `birthDate` is now `personal.birthDate`. A derived `employee.birthDay` (month and day only, no year) is
also exposed as a separate property.
### Banking fields consolidated
`rib`, `iban`, `bic`, and `bankName` are merged into a single `bankAccount` object with `accountIdentifier`, `bankIdentifier`,
`bankName`, and `format` sub-fields.
The bank account is available both on the `employee-personal-record` resource and as an `employee-attribute-definition`
with ID `personal.bankAccount`.
```json v5 BankAccount theme={null}
{
"format": "iban",
"accountIdentifier": "FR1612739000503533694532L71",
"bankIdentifier": "AGRIFRPP",
"bankName": "Crédit Agricole"
}
```
| v3 field | v5 bankAccount. |
| :------------------- | :------------------ |
| *(no v3 equivalent)* | `format` |
| `rib` | `accountIdentifier` |
| `iban` | `accountIdentifier` |
| `bic` | `bankIdentifier` |
| `bankName` | `bankName` |
### Contract dates are objects
`dtContractStart` and `dtContractEnd` are now `employment.start` and `employment.end`, each an object with a nested `date` sub-field
rather than a plain date string:
```json theme={null}
// v3: plain string
"dtContractStart": "2020-01-15"
// v5: nested object
"start": { "date": "2020-01-15" }
```
This structural change will break any code that reads contract dates directly as strings. Update your parsing logic to extract the `date` sub-field.
### Custom fields (`e_*`)
V3 `extendedData` fields (custom extension fields prefixed with `e_`) have their own v5 `employee-attribute-definition` and appear in
`employee-attributes` alongside built-in fields. Query `GET /lucca-api/employee-attribute-definitions` to discover them.
The new employee-attribute-definition created from legacy extended-data kept its ID unchanged (e.g. `e_tShirtSize` remains `e_tShirtSize`)
to preserve continuity.
### Phone numbers: `personalMobile` vs `professionalMobile` is ambiguous
The v3 `personalMobile` and `professionalMobile` fields both map onto a `phoneNumber` named property, but which are on two different resources
(`employee` and `employee-personal-record`):
| v3 field | v5 definition ID | v5 resource |
| :------------------- | :--------------------- | :--------------------------------------------------------------- |
| `personalMobile` | `personal.phoneNumber` | `employee-personal-record` — the employee's private phone number |
| `professionalMobile` | `employee.phoneNumber` | `employee` — the employee's work phone number |
If your v3 integration used a generic "phone" or "mobile" field, make sure you identify which one you need and map to the correct v5 definition.
***
## Field Mapping Reference
### Employee (`employee.*`)
| Legacy field (v3) | New definition ID (v5) | Notes |
| :--------------------- | :------------------------ | :---------------------------------------------------------------------------- |
| `mail` | `employee.email` | |
| `lastName` | `employee.familyName` | |
| `firstName` | `employee.givenName` | |
| `professionalMobile` | `employee.phoneNumber` | |
| `employeeNumber` | `employee.employeeNumber` | Also known as `matricule` in the legacy API |
| `login` | `employee.remoteId` | |
| `picture`, `pictureId` | `employee.portrait` | |
| `id` | `employee.id` | Is a string in v5. |
| *(no v3 equivalent)* | `employee.status` | New property only |
| *(no v3 equivalent)* | `employee.birthDay` | Derived from `birthDate`; exposes `month` and `day` sub-fields only (no year) |
Full reference for the `employee` resource, including all available fields and their types.
***
### Personal Record (`personal.*`)
| Legacy field (v3) | New definition ID (v5) | Notes |
| :------------------------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------- |
| `birthDate` | `personal.birthDate` | Previously a top-level user field |
| `rib`, `iban`, `bic`, `bankName` | `personal.bankAccount` | Consolidated into one object with `accountIdentifier`, `bankIdentifier`, `bankName`, `format` sub-fields |
| `personalEmail` | `personal.email` | |
| `insuranceNumber` | `personal.insuranceNumber` | Sub-fields: `value`, `format` |
| `gender` | `personal.legalGender` | |
| `nationality`, `nationalityId` | `personal.nationalitiesCountryCodes` | Now a list of three-letter country codes (ISO 3166-1 alpha-3) |
| `personalMobile` | `personal.phoneNumber` | |
| `civilTitle` | `personal.title` | |
Full reference for the `employee-personal-record` resource, including all available fields and their types.
***
### Employment (`employment.*`)
| Legacy field (v3) | New definition ID (v5) | Notes |
| :------------------- | :----------------------- | :------------------------------------ |
| `dtContractStart` | `employment.start` | Now an object with a `date` sub-field |
| `dtContractEnd` | `employment.end` | Now an object with a `date` sub-field |
| *(no v3 equivalent)* | `employment.legalEntity` | New property only |
| *(no v3 equivalent)* | `employment.template` | New property only |
| *(no v3 equivalent)* | `employment.document` | New property only |
| *(no v3 equivalent)* | `employment.remoteId` | New property only |
Two probationary period properties are also available on employments, with no v3 equivalent: `probationaryPeriod.initialEndsOn` and `probationaryPeriod.extendedEndsOn`.
Full reference for the `employment` resource, including all available fields and their types.
***
### Job Position (`jobPosition.*`)
| Legacy field (v3) | New definition ID (v5) | Notes |
| :--------------------------------------- | :---------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
| `department`, `departmentId` | `jobPosition.department` | |
| `manager`, `managerId` | `jobPosition.manager` | |
| `legalEntity`, `legalEntityId` | `jobPosition.businessEstablishment` | In the v5 Lucca API, `legalEntity` at the job level maps to the job-position **business establishment**, not the employment legal-entity. |
| `jobQualification`, `jobQualificationId` | `jobPosition.jobQualification` | |
| `csp`, `cspId` | `jobPosition.occupationCategory` | |
| `jobTitle` | `jobPosition.jobTitle` | |
| *(no v3 equivalent)* | `jobPosition.document` | New property only |
| *(no v3 equivalent)* | `jobPosition.endsOn` | New property only |
| *(no v3 equivalent)* | `jobPosition.startsOn` | New property only |
| *(no v3 equivalent)* | `jobPosition.remoteId` | New property only |
Full reference for the `job-position` resource, including all available fields and their types.
***
### Application & Complementary User Data (`user.*`)
These fields were root-level or `applicationData` properties of the legacy user. They retain the `user.*` namespace in the Lucca API because they don't map cleanly to the new `employee`, `employment`, or `job-position` model — most are application-specific settings, approval chains, or computed values. Rather than being reassigned to a new resource, they are preserved under this namespace for compatibility.
| Legacy field (v3) | New definition ID (v5) | Notes |
| :----------------------------------------- | :------------------------------------ | :------------------------------------------------------------- |
| `seniorityDate` | `user.seniorityDate` | |
| `frenchCarTaxHorsePower` | `user.frenchCarTaxHorsePower` | |
| `frenchMotorcyclesTaxHorsePower` | `user.frenchMotorcyclesTaxHorsePower` | |
| `corporateCard` | `user.corporateCard` | |
| `personalCard` | `user.personalCard` | |
| `personalAccount` | `user.personalAccount` | |
| `unitSellPrice` | `user.unitSellPrice` | Deprecated even in v3; consider removing from your integration |
| `allowsElectronicPayslip` | `user.allowsElectronicPayslip` | |
| `site`, `siteId` | `user.siteId` | |
| `directLine` | `user.directLine` | |
| `quote` | `user.quote` | |
| `address` | `user.address` | |
| `cleemyApproverId` | `user.cleemyApproverId` | |
| `culture`, `cultureId` | `user.cultureId` | |
| `calendar`, `calendarId` | `user.calendarId` | |
| `manager2`, `manager2Id` | `user.manager2Id` | |
| `profile_cleemy_{{appInstanceId}}` | `user.cleemyProfile` | Sub-fields: `appInstanceId`, `profileId` |
| `profile_figgo` | `user.figgoProfile` | Sub-fields: `appInstanceId`, `profileId` |
| `{{organizationId}}-cost` (timmi-project) | `user.cost` | Sub-fields: `organizationId`, `value` |
| `{{organizationId}}-price` (timmi-project) | `user.price` | Sub-fields: `organizationId`, `value` |
| `navisionId` | `user.navisionId` | |
| `cegidGescomId` | `user.cegidGescomId` | |
| `bergerLevraultCareerNumber` | `user.bergerLevraultCareerNumber` | |
| `timesheetApproverId`, `timesheetApprover` | `user.timesheetApproverId` | |
| `remoteWorkProfileId`, `remoteWorkProfile` | `user.remoteWorkProfileId` | |
| `employeeAccountingCode` | `user.employeeAccountingCode` | |
| `userAxisValues_{{axisId}}` | `user.axisValues` | Sub-fields: `axisId`, `axisSectionId` |
***
## Further Reading
Migrating from the v4 `/directory/api/4.0/work-contracts` API to v5 employment and job-position resources.
Overview of the Lucca API, versioning, and pagination.
Deep dive into the employee data model and how the resources relate to each other.
How to use the unified employee-attributes endpoint to read any employee property.
Rate and size limits applied to the Lucca API, with best practices for designing bulk sync integrations.
# V4 Work-Contracts to V5 Employments & Job-Positions
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/guides/work-contracts-migration
Migration steps from /directory/api/4.0/work-contracts to the Lucca API employment and job-position resources.
The v4 work-contract API (`/directory/api/4.0/work-contracts`) is deprecated in favour of the **Lucca API** (v5), which replaces the
work-contract (representing the base contract) and work-contract-amendment (representing incremental historized changes to the base contract)
resources with different-typed and historized resources: `employment` and `job-position`.
**This migration is only applicable to read-only integration scenarios.** The Lucca API `employment` and `job-position` resources are currently
**read-only**. If your integration creates, updates, or deletes work-contracts, it cannot be migrated to the Lucca API at this time and you
should keep using the v4 endpoint until a write API becomes available.
No sunset date has been announced for `/directory/api/4.0/work-contracts` yet. Make sure your API key has a valid contact email address so you
receive deprecation notices when a date is set.
If your integration uses `/api/v3/users`, see the dedicated [users migration guide](/api-reference/legacy/directory/guides/users-migration).
***
## What Changes
In v4, a work-contract was a single record holding both the legal contract terms (start and end dates, contract type) and the employment
context (establishment, occupation category). The Lucca API splits this into dedicated resources:
The legal contract: start/end dates, the legal entity, the contract template, and an optional external reference. One employment
covers a continuous period with the company.
The professional role during an employment: business establishment, department, manager, job title, occupation category, and more.
One employment may span **multiple sequential job-positions** when the employee's role changes without the contract ending.
The trial period associated with an employment, if any. It has its own start and end dates, which may differ from the employment's dates.
A uniform endpoint that surfaces any property from the employee, employment, and job-position resources — including custom extension
fields — in a single call. The recommended approach for integrations that want a comprehensive snapshot of an employee's data without
chaining multiple requests.
**Recommended for bulk reads**: rather than querying `employments` and `job-positions` separately, use the
[`employee-attributes`](/api-reference/latest/employee-attributes/get-started) endpoint. It lets you retrieve employment dates,
business establishment, department, manager, and any other property in a **single paginated call** with
`?definition.id=employment.start,jobPosition.department,...&applicability.asOf={TODAY}`. The `applicability` field on each attribute
contains the time window during which the value applies, which replaces the v4 `isApplicable` fallback logic. The `employee-attributes`
endpoint returns at most 1,000 items per page.
**Establishment and role fields moved to job-position**: in v4, fields like `establishmentId` and `spcId` sat directly on the work-contract. In v5, these belong to the **job-position**, not the employment. This means a new job-position is created whenever only the role (not the contract itself) changes — it is no longer necessary to terminate and re-create a contract for role-only changes.
**Snapshots replace diffs**: the v4 work-contract amendment model stored incremental changes on top of a base contract (a "diff" approach). In contrast, **employments and job-positions in v5 are "snapshot" resources** that each record the full state for their period — a job-position stores the complete establishment, occupation category, manager, etc., not just what changed.
***
## First: Switch to OAuth 2.0 and Set the Api-Version Header
First of all, replace your API key header with an OAuth 2.0 bearer token obtained from the Lucca authorization server. You will need to request
the appropriate scopes for the resources you intend to read:
| Resource | Required scope |
| :---------------------------------- | :---------------------------------------- |
| `employment` | `employments.readonly` |
| `job-position` | `job-positions.readonly` |
| `probationary-periods` | `probationary-periods.readonly` |
| `employee-attributes` (values) | `employee-attributes.readonly` |
| `employee-attributes` (definitions) | `employee-attribute-definitions.readonly` |
The `employee-attribute-definitions.readonly` scope is only needed if you want to **discover** attribute IDs programmatically (e.g. to enumerate
all custom fields via `GET /lucca-api/employee-attribute-definitions`). If your integration already knows the definition IDs it needs,
`employee-attributes.readonly` is sufficient.
Refer to the [authentication guide](/documentation/using-api/authentication) for the full OAuth 2.0 flow.
Additionally, the v5 Lucca API is versioned, so you must include the `Api-Version: {VERSION}` HTTP header in all your requests. Currently, the
latest version is `2024-11-01`.
Read more about how to obtain and use OAuth 2.0 bearer tokens.
Read more about how to use the `Api-Version` header to specify the API version.
***
## Notable Breaking Changes
Pay close attention to these semantic changes — they may cause silent data errors if overlooked.
### `establishmentId` moved to job-position
In v4, the establishment (`establishmentId`) was a property of the work-contract itself. In v5, it is a property
of the **job-position** (`businessEstablishment`). As a result, the same employment contract can now cover multiple
establishments across successive job-positions — you no longer need to terminate and re-create a contract simply
because the role changes.
### `legalEntity` vs. `businessEstablishment`
The v4 `establishmentId` maps to the v5 `jobPosition.businessEstablishment`. Meanwhile, `employment.legalEntity`
is a new concept in v5 with no direct v4 equivalent — it is the legal entity on the employment contract, inferred
from the first job-position's business establishment and displayed as "legal-unit" in the Lucca UI.
| V4 field | V5 resource | V5 field | V5 concept |
| :------------------- | :------------- | :---------------------- | :------------------------------------------------------------ |
| `establishmentId` | `job-position` | `businessEstablishment` | Where the employee physically works. |
| *(no v4 equivalent)* | `employment` | `legalEntity` | Legal entity (sometimes called "legal unit") on the contract. |
### Trial period day counts are gone
The v4 `trialPeriodDays` and `renewedTrialPeriodDays` integer fields have no v5 equivalent. Only the computed end dates
(`initialEndsOn` and `extendedEndsOn` on the `probationary-period` resource) are stored.
### Contract dates are now objects
The v4 `startsOn` and `endsOn` were plain date strings. In v5, `employment.start` and `employment.end` are **objects** with a nested
`date` sub-field:
```json theme={null}
// v4: plain string
"startsOn": "2024-01-01"
// v5: nested object
"start": { "date": "2024-01-01" }
```
This structural change will break any code that reads contract dates directly as strings. Update your parsing logic to extract the
`date` sub-field.
### IDs are strings in v5
All v5 resource identifiers are JSON strings (`"id": "514"`) rather than integers (`"id": 514`). Do not use strict
equality (`===`) between v4 integer IDs and v5 string IDs.
### Mapping v4 work-contract IDs to v5 employment IDs
V4 `work-contract.id` and v5 `employment.id` are **not** an exact match, and as a result. If your integration depends on v4 work-contract IDs and you need to map them onto employments, then you should use the combination of the **employment start date** and the **employee's login** (`employee.remoteId`) as a unique identifier to match records. For a given employee, the pair `(employee.remoteId, employment.start.date)` should be unique and lets you correlate v4 and v5 data without relying on IDs.
Alternatively, if your v4 integration used `externalId`, that value is preserved as `employment.remoteId` in v5 — you can filter
employments by `remoteId` to find the corresponding v5 resource.
### `isApplicable` fallback is not replicated in v5 (breaking change)
This change can **silently break** integrations that relied on the v4 fallback behavior. Test thoroughly.
In v4, the `isApplicable: true` flag used a fallback rule: if no contract was current, it marked the next upcoming contract as applicable,
or the last contract if there was none. The v5 `status: "active"` does **not** replicate this fallback — an employee with a gap between
employments will have no `active` employment during that gap. If your integration relied on this fallback, adjust your query logic accordingly.
**Recommended alternative**: use the [`employee-attributes`](/api-reference/latest/employee-attributes/get-started) endpoint with the
`applicability.asOf` query parameter. The `applicability` field on each returned attribute provides `start` and `end` dates that define
when the value applies. This gives you explicit temporal boundaries instead of the implicit v4 fallback logic. For example:
```http theme={null}
GET /lucca-api/employee-attributes?employee.id={ID}&definition.id=employment.start,employment.end&applicability.asOf={TODAY} HTTP/1.1
```
This returns only the attributes whose applicability period includes today's date, effectively giving you the "currently applicable"
employment data without relying on any fallback rule.
***
## Field Mapping Reference
Work-Contract = Employment + Job-Position + Employee-Attribute\[]
| V4 work-contract field | V5 resource | V5 field | Notes |
| :---------------------------- | :----------------------------------------------------- | :------------------------------------ | :-------------------------------------------------------------------------------------- |
| `id` | `employment` | `id` | New ID; v4 and v5 IDs are not the same value — do not assume identity |
| `ownerId` | `employment` / `job-position` | `employee.id` | Both resources reference the employee |
| `externalId` | `employment` | `remoteId` | Renamed; no uniqueness constraint in v5 |
| `typeId` | `employment` | `template.id` | Now a reference to the `employment-template` resource |
| `startsOn` | `employment` | `start.date` | Now a nested object: `{ "date": "YYYY-MM-DD" }` |
| `endsOn` | `employment` | `end.date` | Same wrapping object; `null` for open-ended contracts |
| `isApplicable` | `employment` | `status` | `isApplicable: true` ≈ `status: "active"`. Read-only in both versions |
| `establishmentId` | `job-position` | `businessEstablishment.id` | Moved from the contract to the job-position |
| `spcId` | `job-position` | `occupationCategory.id` | Renamed; still references the same occupation categories |
| `trialPeriodEndDate` | `probationary-period` | `initialEndsOn` | Moved to the separate `probationary-period` resource |
| `trialPeriodEndDate2` | `probationary-period` | `extendedEndsOn` | Same separate resource |
| `trialPeriodDays` | *(no equivalent)* | — | Only the end date is stored in v5; the day count is not |
| `renewedTrialPeriodDays` | *(no equivalent)* | — | Same — only the end date is stored |
| `hiringTypeId` | `employee-attribute` | `e_generated_hiringType` | The hiring type is now stored as an employee attribute. |
| `temporaryContractGroundId` | `employee-attribute` | `e_generated_temporaryContractGround` | The temporary contract ground is now stored as an employee attribute. |
| `internshipSupervisorId` | `employee-attribute` | `e_generated_internshipSupervisorId` | The internship supervisor is now stored as an employee attribute. |
| `terminationReasonId` | `employee-attribute` | `e_generated_terminationReasonId` | The termination reason is now stored as an employee attribute. |
| `createdAt` | `employment` or `job-position` or `employee-attribute` | `createdAt` | Same semantics |
| `lastModifiedAt` | `employment` or `job-position` or `employee-attribute` | `lastUpdatedAt` | Renamed |
| `authorId` / `lastModifierId` | *(no equivalent)* | — | Authorship metadata not exposed in v5 while there's no official "account" API resource. |
Fields migrated to `employee-attributes` with the `e_generated_` prefix are auto-generated attribute definitions created by Lucca when
migrating legacy contract data. Confirm their exact IDs by querying `GET /lucca-api/employee-attribute-definitions`.
Fields that appeared on the v3 user object but were absent from the v4 work-contract — `department`, `manager`, `jobTitle`, `jobQualification` —
are now in the **job-position** resource. Combine an employment query with a job-position query to get the full picture. Or retrieve
everything in one call via the `employee-attributes` endpoint while making sure to set `?applicability.asOf={TODAY}`.
***
## Querying in V5
All collection endpoints enforce cursor-based pagination. Start with `?limit={page_size}&include=links`, then follow the `links.next` URL
in each response to get the next page. Iterate until `links.next` is `null`. The maximum page size is 1,000 items (default 25).
Learn how to paginate through collection endpoints in the Lucca API.
### List all employments for an employee
Add `include=links` to the request — this is required to receive pagination links in the response. Iterate until `links.next` is `null` to retrieve all pages.
```http v4 theme={null}
GET /directory/api/4.0/work-contracts?ownerId={EMPLOYEE_ID} HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
```
```http v5 theme={null}
GET /lucca-api/employments?employee.id={EMPLOYEE_ID}&include=links HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"items": [
{
"id": "4561",
"type": "employment",
"employee": { "id": "416", "type": "employee", "url": "..." },
"status": "active",
"start": { "date": "2024-01-01" },
"end": null,
...
}
],
"links": {
"next": null
}
}
```
If you only need the currently active employment, add `&status=active` to the query parameters.
### Retrieve a single employment by ID
```http theme={null}
GET /lucca-api/employments/{EMPLOYMENT_ID} HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"id": "4561",
"type": "employment",
"url": "https://example.ilucca.net/lucca-api/employments/4561",
"employee": { "id": "416", "type": "employee", "url": "..." },
"legalEntity": { "id": "123", "type": "legal-entity", "url": "..." },
"status": "active",
"start": { "date": "2024-01-01" },
"end": null,
"template": { "id": "4", "type": "employment-template", "url": "..." },
"remoteId": null,
...
}
```
### List all job-positions for an employment
```http theme={null}
GET /lucca-api/job-positions?employment.id={EMPLOYMENT_ID} HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"items": [
{
"id": "74",
"type": "job-position",
"employment": { "id": "4561", "type": "employment", "url": "..." },
"employee": { "id": "416", "type": "employee", "url": "..." },
"status": "active",
"startsOn": "2024-01-01",
"endsOn": null,
"businessEstablishment": { "id": "5", "type": "business-establishment", "url": "..." },
"jobTitle": "Developer",
"manager": { "id": "541", "type": "employee", "url": "..." },
"department": { "id": "32", "type": "department", "url": "..." },
"occupationCategory": { "id": "12", "type": "occupation-category", "url": "..." },
...
}
]
}
```
To get only currently active job-positions, add `&status=active` to the query parameters.
Also note that the `employment` can also be embedded inline using `?include=embedded` if you need employment
details together with the job-position.
### Get the probationary period for an employment
```http theme={null}
GET /lucca-api/probationary-periods?employment.id={EMPLOYMENT_ID} HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
```
If you need employment dates, business establishment, manager, department, and job title all in one call —
rather than chaining separate requests to `employments` and `job-positions` — use the
[`employee-attributes`](/api-reference/latest/employee-attributes/get-started) endpoint while making sure
to set `applicability.asOf={TODAY}`.
***
## Further Reading
Migration guide for the v3 user object, covering personal data, employment, and job-position fields.
How to obtain and use OAuth 2.0 bearer tokens.
Full reference for the v5 employment resource.
Full reference for the v5 job-position resource.
How to use the unified employee-attributes endpoint to read any employee property in a single call.
# List Users
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/list-users
/openapi-specs/directory-v3.yaml get /api/v3/users
Retrieve a list of Users.
By default, former employees are excluded from the response. In order to retrieve them, you may add the `?dtContractEnd=notequal,null` query parameter to your request.
This legacy endpoint has its equivalent in the **Lucca API** (currently in beta): [`GET /lucca-api/employees`](/api-reference/latest/get-employees).
To retrieve all employee data (including extensions) in a single call, you may also use [`GET /lucca-api/employee-attributes`](/api-reference/latest/get-employee-attributes), filterable by employee.
# Update a work-contract
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/patch-work-contract
/openapi-specs/directory-v3.yaml patch /directory/api/4.0/work-contracts/{id}
Update a work-contract by its ID.
# Create a work-contract
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/post-work-contract
/openapi-specs/directory-v3.yaml post /directory/api/4.0/work-contracts
Create a new work-contract.
There are two possible scenarios:
1. The user/employee already exists: in this case, you may first have to set an end date to the previous contract.
2. The user does not already exist:
- **recommended** `POST /api/v3/users?contract=true`: this will create the user and his contract. You may then need to PATCH the contract that was thus created in order to set additional data.
- `POST /api/v3/users?contract=false`: this will create the user without a contract. It will therefore be essential to create your contract via a POST on the contracts API.
# Update a User by id
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/update-a-user-by-id
/openapi-specs/directory-v3.yaml put /api/v3/users/{id}
Update fields of a single User identified by its unique id.
# The user resource
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/user
directory-v3
This legacy resource has its equivalent in the **Lucca API**.
The legacy user is a monolithic resource that only reflects the **current state** of an employee. The Lucca API introduces a richer model that tracks the **full career history** through dedicated, time-bound resources:
* [Employee](/api-reference/latest/employees/employee) — core identity and organizational data.
* [Employee personal record](/api-reference/latest/employees/employee-personal-record) — personal and private information.
* [Employment](/api-reference/latest/employments/employment) — contractual relationship with a legal entity over time (replaces work-contracts).
* [Job position](/api-reference/latest/employments/job-position) — department, manager, job title, and other career data, historized over the employee's tenure.
The [employee-attributes API](/api-reference/latest/get-employee-attributes) also lets you retrieve all data for one or more employees in a single call — core fields, extensions, and application data — similar to what the legacy `?fields=` mechanism offers.
Refer to the [getting started guide](/api-reference/latest/employees/get-started) for a complete overview, or the dedicated [migration guide](/api-reference/legacy/directory/guides/users-migration).
The v3 User is the representation of an employee in the Legacy API.
A v3 user is comprised of:
* a hard-coded set of attributes (e.g `firstName`, `mail`, `managerId`, etc...) called `coreData`;
* a dynamic set of extensions depending on installed applications called `applicationData`. Some of them are nested in a `applicationData` JSON object in read (GET) requests, and some are root properties.
* a dynamic set of custom extensions depending on the Employee Directory configuration called `extendedData`. Their names are prefixed with `"e_"`. For read requests, these are always nested in the `extendedData` attribute.
Examples:
| Name | Description | Type |
| :------------------ | :----------------------------------------------------------- | :---------------- |
| `address` | Employee's personal address. | `CodeData` |
| `e_bloodType` | Employee's blood type. | `ExtendedData` |
| `profile_cleemy_89` | Employee's expenses management profile (for application 89). | `ApplicationData` |
```json JSON theme={null}
// Read
{
"data": {
"id": 2938,
// Dictionary of CoreData
// Root level application data
"frenchMotocyclesTaxHorsePower": "",
"applicationData": {
"profile_cleemy_89": {
"id": 12,
"name": "Regular employee",
"url": "https://example.ilucca.net/api/v3/expenseprofiles-89/12"
}
// Dictionary of ApplicationData
},
"extendedData": {
"e_bloodType": "AB"
// Dictionary of ExtendedData
}
}
}
```
The v3 user existed before the introduction of work-contracts, as a result, contractual information (establishment/legal-entity, contract start and end dates, etc...) now comes from the ["applicable" work-contract](./work-contract).
You may not be able to retrieve all properties, as you may not have access to every single one of them.
### About Extended Data(s)
[Read more about extended-data](./guides/extended-data)
### About User Axis Values
User axis values can be used for matching users and axis-sections. Common use-case is giving each user a cost center, which are `axis-sections` in a "Cost Center" `axis`. [Read more about axes and axis-sections](../organization/axis-sections/get-started).
In order to attach an axis-section of a given axis to a user, you may use dynamic properties whose key must match:
`{ "userAxisValues_{{axisId}}": {"id": {{axisSectionId}} }`.
For example, if user with ID 416 belongs to the cost center which corresponds to the axis-section with ID 35 in the axis with ID 2:
```json theme={null}
{
"id": 416,
...
"userAxisValues_2": {
"id": 35
}
}
```
### About Application Data
Application-data are custom extensions to the user/employees defined by installed applications on your Lucca account.
**About Lucca Expenses Profile**
Expenses management profile in Lucca Expenses.
Assigning a Lucca Expenses profile to a user is handled through a property whose name is dynamic, as it must conform to the pattern `"profile_cleemy_{{appInstanceId}}` where `appInstanceId` is the ID of the corresponding Cleemy Expense appplication instance (there can be more than one Cleemy Expenses application installed on any given account).
```json theme={null}
{
"profile_cleemy_12": {
"id": 34,
"name": "Generic profile",
"url": "..."
}
}
```
# The work-contract resource
Source: https://developers.luccasoftware.com/api-reference/legacy/directory/work-contract
directory-v3
This legacy resource has its equivalent in the **Lucca API**.
The work-contract has been replaced by two complementary resources that together provide a complete and historized view of the employee's contractual situation:
* [Employment](/api-reference/latest/employments/employment) — the contractual relationship between an employee and a legal entity (contract type, start/end dates, trial period, etc.).
* [Job position](/api-reference/latest/employments/job-position) — the position held within an employment: department, manager, job title, establishment, and other career data. Multiple job positions can exist within a single employment, tracking career changes over time.
Refer to the [getting started guide](/api-reference/latest/employees/get-started) for a complete overview, or the dedicated [migration guide](/api-reference/legacy/directory/guides/work-contracts-migration).
The `work-contract` resource models a formal employment relationship between an `employee` (or `user`) and an `establishment`.
Each `employee` is required to have at least one associated `work-contract`. Multiple contracts are supported, provided their date ranges do not overlap.
A `work-contract` determines the employee’s **active status** within the system. Access to the employee’s Lucca account is granted exclusively within the valid date range of at least one active work-contract. Outside of these bounds, account access is disabled.
Additionally, a `work-contract` contains data describing the employment terms, such as:
* Contract type (e.g., permanent, fixed-term);
* Presence of a trial period;
* Work duration;
* etc…
# Download the OpenAPI specifications
Source: https://developers.luccasoftware.com/api-reference/legacy/download
The OpenAPI specifications of the Lucca Legacy APIs are available for download. They can be used to generate client libraries, create API documentation, and more.
Files API
Organization API
Core HR API
Job Offers API
Recruitment API
Expenses API
Invoices API
Absences API
Office API
Project API
Schedule API
Timesheet V3 API
Timesheet V4 API
Meal Voucher API
Compensation API
Training API
# The file resource
Source: https://developers.luccasoftware.com/api-reference/legacy/files/file
lucca-files
# Upload a file
Source: https://developers.luccasoftware.com/api-reference/legacy/files/post-upload
/openapi-specs/lucca-files.yaml post /lucca-files/api/uploads
Upload a file.
# Using Files
Source: https://developers.luccasoftware.com/api-reference/legacy/files/using-files
Some API resources may have relationships with files. Examples may be:
* a user picture ;
* a work-contract attachment ;
* etc...
First upload a file through the file-upload API endpoint.
```http HTTP Request theme={null}
POST /lucca-files/api/uploads HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
Content-Type: multipart/form-data; boundary=---abc
-----abc
Content-Disposition: form-data; name=""; filename="test.pdf"
-----abc--
```
```curl Curl Request theme={null}
curl --location 'https://example.ilucca.net/lucca-files/api/uploads' \
--header 'Authorization: lucca application={API_KEY}' \
--header 'Accept: application/json' \
--form '=@"/C:/foo/bar/test.pdf"'
```
```json Response theme={null}
{
"id": "d18261af-3b70-442f-94c2-3995cc8ca02c",
"name": "test.pdf",
"createdAt": "2024-11-21T14:54:14.1273553Z",
"deletedAt": null,
"contentLength": 3242,
"contentType": "application/pdf",
"extension": ".pdf",
"totalPages": 1
}
```
Make sure to keep the id given in the response, you'll need it later.
You then need to set the uploaded file id on the corresponding resource.
```json User picture example theme={null}
PUT /api/v3/users/416 HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
Content-Type: application/json
{
"picture": {
"id": "618d5bac-2f38-4d5b-8ba3-981733dffeff"
}
}
```
```json ExtendedData example theme={null}
PUT /api/v3/users/:idUser?fields=extendedData,id HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
Content-Type: application/json
{
"e_Visite-medicale": [
{
"value": {
"e_Date-de-la-visite": { "value": null },
"e_Attestation": {
"value": { "id": "552cb7db-b7e2-4f39-a675-c4b140f41eea" } // File id
},
"e_Convocation": { "value": null },
"e_Date-de-la-prochaine-visite": { "value": null },
"e_Preconisations-medicales": { "value": null }
},
"isNew": true,
"id": 0
}
]
}
```
# Introduction
Source: https://developers.luccasoftware.com/api-reference/legacy/introduction
Earlier versions of the Lucca APIs, still available during the transition to the Lucca API.
The **[Lucca API](/api-reference/latest/introduction)** is our current public API. It offers a stable, versioned contract with
OAuth 2.0 authentication and long-term support guarantees.
If you are starting a new integration, we recommend using the Lucca API directly.
The previous APIs documented in this section predate the Lucca API. They were originally designed for
internal use and made available publicly over time. As such, they differ from the Lucca API in a few ways:
* They follow [different conventions](./api-generations) depending on when they were introduced.
* They are not versioned — changes may be applied without a formal deprecation cycle, but we will still notify you in advance whenever possible.
Make sure a valid contact email is associated with your API keys so you can be notified ahead of time.
* They do not use a modern authentication scheme (API keys rather than OAuth 2.0).
These endpoints are **production-ready** and actively used by thousands of customers every day.
You can safely build integrations on them. As the Lucca API expands its coverage, equivalent
endpoints will become available and migration guides will be provided.
Download the OpenAPI specifications of the Lucca Legacy APIs to generate client libraries, create API documentation, and more.
# Limits
Source: https://developers.luccasoftware.com/api-reference/legacy/limits
Per domain (i.e. Lucca account, e.g. "[https://example.ilucca.net](https://example.ilucca.net)"). Otherwise you'll get a `429 Too Many Requests`.
Our server cuts the connection and returns an error if the request body is too large.
Any request that takes more than 5 seconds will be aborted by our server.
# The axis resource
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/axis-sections/axis
organization-v3
# The axis-section resource
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/axis-sections/axis-section
organization-v3
Read more about axis-sections in the [Get Started](./get-started) section.
# Create a new AxisSection
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/axis-sections/create-a-new-axissection
/openapi-specs/organization-v3.yaml post /api/v3/axisSections
Create a new AxisSection.
# Delete an AxisSection by id
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/axis-sections/delete-an-axissection-by-id
/openapi-specs/organization-v3.yaml delete /api/v3/axisSections/{id}
Delete an AxisSection by id
# Get an AxisSection by id
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/axis-sections/get-an-axissection-by-id
/openapi-specs/organization-v3.yaml get /api/v3/axisSections/{id}
Retrieve a single AxisSection by its unique identifier.
# Get Started With Axis-Sections
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/axis-sections/get-started
Learn more about axes & axis-sections.
### Definition
The `axis-sections` are API resources that represent individual elements within a list. They serve as analytical dimensions, used to label [`time-entries`](../../timmi-timesheet/time-entries/time-entry) or [`expenses`](../../cleemy-expenses/expenses/expense-temp-item). These are fully customizable objects, which means they can represent various concepts such as projects, clients, cost centers, or components of an organization’s structure.
Each `axis-section` belongs to an `axis`, which represents the list to which the sections belong. For instance, an `axis` named "Projects" would contain `axis-sections` representing each of the company’s projects.
You then usually will retrieve axis-sections while filtering on their axis:
```http GET axis-sections theme={null}
GET /api/v3/axissections?axisId={AXIS_ID} HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
```
Axes can also define parent-child relationships with other axes. For example, the "Projects" `axis` might be a child of a "Clients" `axis`. In this setup, each `axis-section` within the "Projects" `axis` can reference an `axis-section` from the "Clients" `axis` as its parent.
These relationships can be:
* **One-to-many** (1-N): each child has a single parent, or
* **Many-to-many** (N-N): a child can have multiple parents.
As a result, you may want to only retrieve "projects" `axis-sections` that belong to a given "client" `axis-section`:
```http GET axis-sections theme={null}
GET /api/v3/axissections?axisId={AXIS_ID}&parentAxisSections.id=containsall,{PARENT_IDS...} HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
```
The `containsall` keyword above gives you a way of filtering `axis-sections` whose N parents list contains all of the given `axis-section` Ids ("AND"). Otherwise, `?parentAxisSections.id=1,2,3` would function as a "OR" (i.e. contains at least one of them).
The relationship type is defined by the `isNNRelation` property on the `axis` object.
For example, a many-to-many relationship would apply if there is an extra `axis` called "Tasks" that is a child of the "Projects" `axis`—assuming that a task can be associated with multiple projects.
```json Axes theme={null}
// GET /api/v3/axes HTTP/1.1
{
"data": {
"items": [
{
"id": 1,
"name": "Clients",
"parentAxisId": null,
"isNNRelation": false
},
{
"id": 2,
"name": "Projects",
"parentAxisId": 1,
"isNNRelation": false
},
{
"id": 3,
"name": "Tasks",
"parentAxisId": 2,
"isNNRelation": true
}
]
}
}
```
```json Axis-sections theme={null}
// GET /api/v3/axisSections HTTP/1.1
{
"data": {
"items": [
{
"id": 1,
"name": "Acme Corporation",
"axis": {
"id": 1,
"name": "Clients",
"parentAxisId": null,
"isNNRelation": false
},
"parentAxisSections": []
},
{
"id": 2,
"name": "My awesome project",
"axis": {
"id": 2,
"name": "Projects",
"parentAxisId": 1,
"isNNRelation": false
},
"parentAxisSections": [
{
"id": 1,
"axisId": 1
}
]
},
{
"id": 3,
"name": "My #2 project",
"axis": {
"id": 2,
"name": "Projects",
"parentAxisId": 1,
"isNNRelation": false
},
"parentAxisSections": [
{
"id": 1,
"axisId": 1
}
]
},
{
"id": 4,
"name": "User tests",
"axis": {
"id": 3,
"name": "Tasks",
"parentAxisId": 2,
"isNNRelation": true
},
"parentAxisSections": [
{
"id": 2,
"axisId": 2
},
{
"id": 3,
"axisId": 2
}
]
}
]
}
}
```
Given that this relationship can be "many-to-many", objects that reference `axis-sections` (e.g. `expenses` and `time-entries`) usually do the whole tree branch (i.e. the client, project and task `axis-sections`) rather than just the lowest level leaf.
### Setting an Axis-Section Name
When creating a new axis-section (POST) or updating its name (PUT), you should set the `multilingualName` field, which allows you to specify it in multiple languages.
The multilingualName may be formatted either as:
* A JSON object,
* A string with `"[]"` or `"|"` delimiters.
```json Object theme={null}
{
"multilingualName": {
"fr-FR": "Nom en Français",
"en-US": "Name in English"
}
}
```
```json String [] theme={null}
{
"multilingualName": "[fr-FR][Nom en Français][en-US][Name in English]"
}
```
```json String | theme={null}
{
"multilingualName": "fr-FR|Nom en Français||en-US|Name in English"
}
```
As a result, the following characters should be avoided in the actual name of the axis-sections: `"["`, `"]"` and `"|"`. Besides, slashes `"/"` and backslashes `"\"` might also cause problems.
# List Axes
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/axis-sections/list-axes
/openapi-specs/organization-v3.yaml get /api/v3/axes
List all Axes satisfying query filters.
# List AxisSections
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/axis-sections/list-axissections
/openapi-specs/organization-v3.yaml get /api/v3/axisSections
List all AxisSections satisfying query filters.
# Update an AxisSection by id
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/axis-sections/update-an-axissection-by-id
/openapi-specs/organization-v3.yaml put /api/v3/axisSections/{id}
Partial or complete update to an existing AxisSection.
# The department resource
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/departments/department
This API endpoint and model is deprecated in favor of new ones (see below).
# The department resource
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/departments/department-v4
# Get Departement by id (v3)
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/departments/get-departement-by-id-v3
/openapi-specs/organization-v3.yaml get /api/v3/departments/{id}
Retrieve a single Department by its unique identifier.
# List Departements as a tree
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/departments/list-departements-as-a-tree
/openapi-specs/organization-v3.yaml get /api/v3/departments/tree
List all departments as a tree.
Remark: first node is always empty and represents the starting point
of the tree as multiple departments might be set to the highest level.
# List departments
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/departments/list-departments
/openapi-specs/organization-v3.yaml get /organization/structure/api/departments
List departments.
# List Departments (v3)
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/departments/list-departments-v3
/openapi-specs/organization-v3.yaml get /api/v3/departments
List all Departments satisfying query filters.
# Retrieve a department
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/departments/retrieve-a-department
/openapi-specs/organization-v3.yaml get /organization/structure/api/departments/{id}
Retrieve a department by its ID.
# The establishment resource
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/establishments/establishment
organization-v3
# List establishments
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/establishments/list-establishments
/openapi-specs/organization-v3.yaml get /organization/structure/api/establishments
Retrieve a list of establishments.
# Migrating Departments to the Lucca API (v5)
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/guides/departments-migration
Migration steps from the V3 or V4 departments APIs to the Lucca API (v5) department resource.
The V3 departments API is deprecated. The following routes will stop responding starting from **September 2026**:
* `GET /api/v3/departments`
* `GET /api/v3/departments/{id}`
* `GET /api/v3/departments/tree`
The V4 departments API has no announced sunset date, but the Lucca API (V5) is the long-term strategic target.
If you are on V3 and want a lower-friction interim step (same API-key authentication), you may migrate to V4 first —
but a future migration to the V5 Lucca API will still be required.
The V5 Lucca API departments API endpoints are **stable** and will not undergo breaking changes. It supports both read and
write operations. We recommend moving to the Lucca API as soon as possible to take advantage of its improved performance,
richer data model, and long-term support.
The V3 `GET /api/v3/departments/tree` endpoint has **no equivalent** in the V5 Lucca API. It was removed because its
tree structure made pagination impossible to enforce, causing serious performance issues in large organizations.
To reconstruct a tree client-side, page through `GET /lucca-api/departments` and use each department's `parent.id` field
to build the hierarchy.
***
## Before You Start: Switch to OAuth 2.0
Regardless of your starting version, the Lucca API requires an **OAuth 2.0 bearer token** in place of the legacy API key.
Request the scope that matches your use case:
| Operation | Required scope |
| :----------------------------------------- | :------------------------------------------ |
| Read departments | `departments.readonly` |
| Create or update departments | `departments.readwrite` |
| Read employees (for department membership) | `employees.readonly job-positions.readonly` |
Additionally, all requests must include the `Api-Version: 2024-11-01` HTTP header.
A single OAuth 2.0 token can carry multiple scopes. Combine all the scopes you need in one token request —
for example: `scope=departments.readonly employees.readonly job-positions.readonly`.
How to obtain and use OAuth 2.0 bearer tokens with the Lucca authorization server.
How to use the `Api-Version` header to specify the API version.
***
## Pick Your Starting Version
### Quick Start
Request a bearer token with the `departments.readonly` scope (add `departments.readwrite` if needed). See the auth section above.
Include `Api-Version: 2024-11-01` on every request.
Swap `GET /api/v3/departments?paging=0,1000` for `GET /lucca-api/departments?include=links`. Page through results using the cursor token in `links.next`.
Refer to the [field mapping reference](#field-mapping-reference) below for all changes.
The `users`, `currentUsers`, and `currentUsersCount` fields no longer appear on department objects.
Use `GET /lucca-api/employees?applicableJobPosition.department.id={id}&status=active&include=totalCount` instead.
***
### What Changes
* **Authentication**: v3 uses a proprietary API key; the Lucca API uses **OAuth 2.0 bearer tokens**.
* **Versioning**: requests must include the `Api-Version: 2024-11-01` HTTP header.
* **`isActive` → `isArchived`**: the boolean is **semantically inverted**.
* **`code` → `remoteId`**.
* **`parentId` is now `parent`**: was an opaque hierarchy path string (e.g. `"/1/2/"`); now a typed `{ id, type, url }` reference object.
* **IDs become strings**: integers in v3 (`32`), strings in v5 (`"32"`).
* **No embedded `users`/`currentUsers`**: use the `links.activeEmployees` or the `employees` API endpoint filtered by department ID.
* **Pagination**: v3 used `?paging={offset},{limit}`; the Lucca API uses cursor tokens. Max **100 items per page** (default 25).
* **Write operations (new in v5)**: `POST` and `PATCH` are now available. v3 had no write endpoints.
How cursor-based pagination works in the Lucca API.
***
### Notable Breaking Changes
Pay close attention to these changes — some may cause silent data errors if overlooked.
#### `isActive` is now `isArchived` (inverted semantics)
| v3 | v5 | Meaning |
| :---------------- | :------------------ | :----------------------------- |
| `isActive: true` | `isArchived: false` | Department is in use |
| `isActive: false` | `isArchived: true` | Department is no longer in use |
A department can only be archived when no employees are currently attached to it.
#### IDs became strings in v5
```json theme={null}
// v3: integer
{ "id": 32 }
// v5: string
{ "id": "32" }
```
The numeric value is preserved. Update any code that stores or compares department IDs to treat them as strings.
#### `parent` became a typed reference object in v5
In v3, `parentId` in the response was an **opaque hierarchy path string** (e.g. `"/1/2/"`), not a numeric ID. In v5, the parent is a typed reference:
```json theme={null}
// v3 response
{ "parentId": "/1/2/" }
// v5 response
{ "parent": { "id": "12", "type": "department", "url": "https://example.ilucca.net/lucca-api/departments/12" } }
```
Root-level departments have `"parent": null`. Use `parent.id` to navigate the hierarchy.
#### Field `code` was renamed `remoteId`
Map v3 `code` to v5 `remoteId`. Use it for external identifiers.
#### Embedded `users` and `currentUsers` fields were removed
v3 department objects included `users`, `currentUsers`, and `currentUsersCount` directly on the department. In v5, you may either
follow the `activeEmployees` link (only returned when `include=links` is specified) or build the link to the employees endpoint manually:
```json ActiveEmployees link theme={null}
{
"id": "32",
...,
"links": {
"activeEmployees": {
"href": "https://example.ilucca.net/lucca-api/employees?applicableJobPosition.department.id=32&status=active&include=totalCount"
}
}
}
```
```http Building the link manually theme={null}
GET /lucca-api/employees?applicableJobPosition.department.id={DEPARTMENT_ID}&status=active&include=links,totalCount HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
```
The `totalCount` in the response is the equivalent of `currentUsersCount`. Each department also exposes a pre-built `links.activeEmployees.href` pointing to this query.
***
### Migration by Use Case
#### 1. List All Departments
```http V3 theme={null}
GET /api/v3/departments?paging=0,1000 HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
{
"data": {
"items": [
{
"id": 32,
"name": "Finances",
"code": "FIN",
"hierarchy": "/1/",
"parentId": null,
"isActive": true,
"level": 1,
"sortOrder": 1,
"headID": 416,
"currentUsersCount": 12
}
]
}
}
```
```http V5 theme={null}
GET /lucca-api/departments?include=links&limit=25 HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"type": "departments",
"url": "https://example.ilucca.net/lucca-api/departments?limit=25",
"items": [
{
"id": "32",
"type": "department",
"url": "https://example.ilucca.net/lucca-api/departments/32",
"name": "Finances",
"description": null,
"remoteId": "FIN",
"isArchived": false,
"parent": null,
"level": 1,
"sortOrder": 0,
"manager": {
"id": "416",
"type": "employee",
"url": "https://example.ilucca.net/lucca-api/employees/416"
},
"owningApplication": null,
"links": {
"activeEmployees": {
"href": "https://example.ilucca.net/lucca-api/employees?applicableJobPosition.department.id=32"
}
}
}
],
"links": {
"prev": null,
"next": { "href": "https://example.ilucca.net/lucca-api/departments?page=!sdk87Sdh&limit=25" }
}
}
```
The v5 `department.id` value is numerically the same as the v3 value, though v5 represents it as a string (`"32"`) rather than an integer (`32`) in JSON.
#### 2. Retrieve a Department by ID
```http V3 theme={null}
GET /api/v3/departments/32 HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
{
"data": {
"id": 32,
"name": "Finances",
"code": "FIN",
"hierarchy": "/1/",
"parentId": null,
"isActive": true,
"level": 1,
"sortOrder": 1,
"headID": 416
}
}
```
```http V5 theme={null}
GET /lucca-api/departments/32 HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"id": "32",
"type": "department",
"url": "https://example.ilucca.net/lucca-api/departments/32",
"name": "Finances",
"description": null,
"remoteId": "FIN",
"isArchived": false,
"parent": null,
"level": 1,
"sortOrder": 0,
"manager": {
"id": "416",
"type": "employee",
"url": "https://example.ilucca.net/lucca-api/employees/416"
},
"owningApplication": null,
"links": {
"activeEmployees": {
"href": "https://example.ilucca.net/lucca-api/employees?applicableJobPosition.department.id=32"
}
}
}
```
#### 3. Filter by Active / Archived Status
| Intent | v3 | v5 |
| :------------ | :-------------------------------------- | :-------------------------------- |
| Active only | Filter client-side on `isActive: true` | `?isArchived=false` |
| Inactive only | Filter client-side on `isActive: false` | `?isArchived=true` |
| All | All returned by default | Same behavior (omit `isArchived`) |
#### 4. Navigate the Hierarchy
V3 supported a `?parentId={integer}` query filter to retrieve direct children of a department. v5 has no server-side equivalent — page through all departments and group by `parent.id` client-side. The `level` field (1 = root) and `sortOrder` (0-based among siblings) help reconstruct the correct display order.
#### 5. Find Employees in a Department
V3 embedded `currentUsers` directly on each department object. In v5, use `activeEmployees` link (to obtain it, add `?include=links`) or query the employees endpoint directly:
```http theme={null}
GET /lucca-api/employees?applicableJobPosition.department.id=32&status=active&include=links,totalCount HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
```
The `totalCount` field in the response is the equivalent of the old `currentUsersCount`.
You need the `employees.readonly` and `job-positions.readonly` scopes to query the employees endpoint, even if you only want the count.
***
### Field Mapping Reference
| v3 field | v5 field | Notes |
| :-------------------- | :---------------------- | :----------------------------------------------------------- |
| `id` | `id` | String in v5 (integer in v3) |
| *(new in v5)* | `type` | Always `"department"` |
| `url` | `url` | Self-link URI |
| `name` | `name` | |
| `description` | `description` | |
| `code` | `remoteId` | |
| `hierarchy` | *(removed)* | Opaque internal path; do not parse or store |
| `parentId` | `parent` | Now a `{ id, type, url }` reference (`null` if root) |
| `isActive` | `isArchived` | Semantics **inverted** |
| `position` | *(removed)* | Replaced by `level` and `sortOrder` |
| `level` | `level` | Min 1 in v5; root = 1 |
| `sortOrder` | `sortOrder` | 0-based index among siblings in v5 |
| `headID` | `manager` | Now a `{ id, type, url }` reference |
| `head` *(SimpleUser)* | `manager` | Full reference object in v5 |
| `owningApplication` | `owningApplication` | Set by Lucca apps; write attempts rejected with `403` if set |
| `users` | *(removed)* | Query the employees endpoint |
| `currentUsers` | *(removed)* | Use `links.activeEmployees` or query the employees endpoint |
| `currentUsersCount` | *(removed)* | Add `?include=totalCount` to the employee query |
| *(new in v5)* | `links.activeEmployees` | Pre-built link to active employees in this department |
### Quick Start
Request a bearer token with the `departments.readonly` scope (add `departments.readwrite` if needed). See the auth section above.
Include `Api-Version: 2024-11-01` on every request.
Swap `GET /organization/structure/api/departments?limit=10` for `GET /lucca-api/departments?include=links`. The response envelope changes: `next` moves from a bare string to `links.next.href`.
Refer to the [field mapping reference](#field-mapping-reference-2) below for all changes.
***
### What Changes
* **Authentication**: v4 uses a proprietary API key; the Lucca API uses **OAuth 2.0 bearer tokens**.
* **Paging** is cursor-based in the Lucca API, not offset-based. Max **100 items per page** (default 25).
* **Versioning**: requests must include the `Api-Version: 2024-11-01` HTTP header.
* **Endpoint path**: `GET /organization/structure/api/departments` → `GET /lucca-api/departments`.
* **Response envelope**: `{ items, count, prev, next }` → `{ type, url, items, totalCount, links: { prev, next } }`.
* **`isActive` → `isArchived`**: the boolean is **semantically inverted**.
* **`code` was renamed `remoteId`**: use it for external identifiers.
* **`parentLuccaId` is now `parent`**: integer ID replaced by a typed `{ id, type, url }` reference object.
* **IDs are strings**: integers in v4 (`32`), strings in v5 (`"32"`).
* **`createdAt` / `lastModifiedAt` removed**: these audit timestamps are not available in v5.
* **`sortOrderInSiblings` → `sortOrder`**: same meaning, renamed.
* **Write operations (new in v5)**: `POST` and `PATCH` are now available. v4 had no write endpoints.
How cursor-based pagination works in the Lucca API.
***
### Notable Breaking Changes
Pay close attention to these changes — some may cause silent data errors if overlooked.
#### `isActive` became `isArchived` (inverted semantics)
| V4 | v5 | Meaning |
| :---------------- | :------------------ | :----------------------------- |
| `isActive: true` | `isArchived: false` | Department is in use |
| `isActive: false` | `isArchived: true` | Department is no longer in use |
A department can only be archived when no employees are currently attached to it.
#### IDs became strings in v5
```json theme={null}
// V4: integer
{ "id": 32 }
// v5: string
{ "id": "32" }
```
The numeric value is preserved. Update any code that stores or compares department IDs to treat them as strings.
#### `parentLuccaId` replaced with `parent` — an object reference
v4 exposed the parent department's numeric ID as `parentLuccaId`. In v5, this is replaced by a typed reference object:
```json theme={null}
// V4 response
{
"parentId": "/1/2/",
"parentLuccaId": 12
}
// v5 response
{
"parent": { "id": "12", "type": "department", "url": "https://example.ilucca.net/lucca-api/departments/12" }
}
```
Root-level departments have `"parent": null`. The opaque `parentId` path string is removed entirely.
#### `code` renamed `remoteId`
Use `remoteId` for external identifiers.
#### `createdAt` and `lastModifiedAt` removed
These audit timestamps are not available in v5.
#### Response envelope changed
The collection response structure differs between v4 and v5:
```json theme={null}
// V4
{
"items": [ ... ],
"count": 11,
"prev": null,
"next": "!cursorToken"
}
// v5
{
"type": "departments",
"url": "https://example.ilucca.net/lucca-api/departments?limit=25",
"items": [ ... ],
"totalCount": 11,
"links": {
"prev": null,
"next": { "href": "https://example.ilucca.net/lucca-api/departments?page=!sdk87Sdh&limit=25" }
}
}
```
In v5, `prev` and `next` are nested under a `links` object and are `{ href }` objects rather than bare strings.
`count` is renamed `totalCount` and only returned when you add `totalCount` to the `?include=` parameter.
***
### Migration by Use Case
#### 1. List All Departments
```http V4 theme={null}
GET /organization/structure/api/departments?limit=10 HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
{
"items": [
{
"id": 32,
"name": "Finances",
"code": "FIN",
"description": null,
"parentId": "/1/",
"parentLuccaId": null,
"hierarchy": "/1/",
"isActive": true,
"headId": 416,
"createdAt": "2020-01-01T00:00:00+00:00",
"lastModifiedAt": "2024-03-15T10:00:00+00:00",
"sortOrderInSiblings": null
}
],
"count": 11,
"prev": null,
"next": "!nextCursorToken"
}
```
```http V5 theme={null}
GET /lucca-api/departments?include=links&limit=25 HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"type": "departments",
"url": "https://example.ilucca.net/lucca-api/departments?limit=25",
"items": [
{
"id": "32",
"type": "department",
"url": "https://example.ilucca.net/lucca-api/departments/32",
"name": "Finances",
"description": null,
"remoteId": "FIN",
"isArchived": false,
"parent": null,
"level": 1,
"sortOrder": 0,
"manager": {
"id": "416",
"type": "employee",
"url": "https://example.ilucca.net/lucca-api/employees/416"
},
"owningApplication": null,
"links": {
"activeEmployees": {
"href": "https://example.ilucca.net/lucca-api/employees?applicableJobPosition.department.id=32"
}
}
}
],
"links": {
"prev": null,
"next": { "href": "https://example.ilucca.net/lucca-api/departments?page=!sdk87Sdh&limit=25" }
}
}
```
To paginate in v5, follow the full URL in `links.next.href`. Add `totalCount` to the `include` parameter to also receive the total count: `?include=links,totalCount`.
#### 2. Retrieve a Department by ID
```http V4 theme={null}
GET /organization/structure/api/departments/32 HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
{
"id": 32,
"name": "Finances",
"code": "FIN",
"description": null,
"parentId": "/1/",
"parentLuccaId": null,
"hierarchy": "/1/",
"isActive": true,
"headId": 416,
"createdAt": "2020-01-01T00:00:00+00:00",
"lastModifiedAt": "2024-03-15T10:00:00+00:00",
"sortOrderInSiblings": null
}
```
```http V5 theme={null}
GET /lucca-api/departments/32 HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"id": "32",
"type": "department",
"url": "https://example.ilucca.net/lucca-api/departments/32",
"name": "Finances",
"description": null,
"remoteId": "FIN",
"isArchived": false,
"parent": null,
"level": 1,
"sortOrder": 0,
"manager": {
"id": "416",
"type": "employee",
"url": "https://example.ilucca.net/lucca-api/employees/416"
},
"owningApplication": null,
"links": {
"activeEmployees": {
"href": "https://example.ilucca.net/lucca-api/employees?applicableJobPosition.department.id=32"
}
}
}
```
#### 3. Filter by Active / Archived Status
| Intent | v4 | v5 |
| :------------ | :-------------------------------------- | :-------------------------------- |
| Active only | Filter client-side on `isActive: true` | `?isArchived=false` |
| Inactive only | Filter client-side on `isActive: false` | `?isArchived=true` |
| All | All returned by default | Same behavior (omit `isArchived`) |
#### 4. Navigate the Hierarchy
v4 had no server-side parent filter either. Continue paging through all departments and grouping client-side —
replace `parentLuccaId` lookups with `parent.id`. The `level` and renamed `sortOrder` (previously `sortOrderInSiblings`)
fields provide the same ordering information.
#### 5. Find Employees in a Department
Each department's `links.activeEmployees.href` pre-builds the URL for you, or you can construct it directly:
```http theme={null}
GET /lucca-api/employees?applicableJobPosition.department.id=32&status=active&include=links,totalCount HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
```
***
### Field Mapping Reference
| v4 field | v5 field | Notes |
| :------------------------- | :---------------------- | :----------------------------------------------------------- |
| `id` | `id` | String in v5 (integer in v4) |
| *(new in v5)* | `type` | Always `"department"` |
| *(new in v5)* | `url` | Self-link URI |
| `name` | `name` | |
| `code` | `remoteId` | Use it for external identifiers |
| `description` | `description` | |
| `hierarchy` | *(removed)* | Opaque internal path; do not parse or store |
| `parentId` *(path string)* | *(removed)* | Opaque hierarchy path; replaced by `parent` reference |
| `parentLuccaId` | `parent` | Now a full `{ id, type, url }` reference; `null` if root |
| `isActive` | `isArchived` | Semantics **inverted** |
| `headId` | `manager.id` | Now a full `{ id, type, url }` reference |
| *(new in v5)* | `level` | Depth in tree; root = 1 |
| `sortOrderInSiblings` | `sortOrder` | Renamed; 0-based index among siblings |
| *(new in v5)* | `owningApplication` | Set by Lucca apps; write attempts rejected with `403` if set |
| `createdAt` | *(removed)* | Not available in v5 |
| `lastModifiedAt` | *(removed)* | Not available in v5 |
| *(new in v5)* | `links.activeEmployees` | Pre-built link to active employees in this department |
***
## Writing Departments (New in v5)
Neither v3 nor v4 officially exposed write endpoints. The Lucca API introduces `POST` (create) and `PATCH` (update) operations,
both requiring the `departments.readwrite` scope.
There is no `DELETE` endpoint for departments. To decommission a department, set `isArchived: true` via `PATCH`.
A department can only be archived when no employees are currently attached to it.
### Create a Department
```http V5 theme={null}
POST /lucca-api/departments HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Content-Type: application/json
{
"name": "Legal",
"description": "Legal & Compliance team",
"remoteId": "LEGAL",
"parent": { "id": "32" },
"manager": { "id": "420" },
"sortOrder": 0
}
```
On success, the server responds with `201 Created`, the full department representation, and a `Location` header pointing to the new resource URL.
### Update a Department
Use `PATCH` with a partial body — only the fields you include will be updated:
```http V5 theme={null}
PATCH /lucca-api/departments/55 HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Content-Type: application/json
{
"name": "Legal & Compliance",
"manager": { "id": "421" }
}
```
***
## Further Reading
Overview of the Lucca API, versioning, and pagination.
Full reference for the Lucca API department resource.
How to obtain and use OAuth 2.0 bearer tokens.
Cursor-based pagination in the Lucca API, with examples.
Rate and size limits applied to the Lucca API.
# Migrating to Lucca API (v5) Business-Establishments & Legal-Entities
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/guides/establishments-migration
Migration steps from V3 legal-entities or V4 establishments to the Lucca API (v5) business-establishments and legal-entities.
The V3 legal-entities API is deprecated. The following routes will stop responding starting from **September 2026**:
* `GET /api/v3/legalentities`
* `GET /api/v3/legalentities/{id}`
The V4 APIs have no announced sunset date, but the Lucca API (V5) is the long-term strategic target.
If you are on V3 and want a lower-friction interim step (same API-key authentication), you may migrate to V4 first —
but a future migration to the V5 Lucca API will still be required.
* `GET /organization/structure/api/establishments`
* `GET /organization/structure/api/legal-units`
The V5 Lucca API business-establishments and legal-entities endpoints are **stable** and will not undergo breaking changes.
We recommend moving to the Lucca API as soon as possible to take advantage of its improved performance, richer data model,
and long-term support.
**Naming change — read carefully.** What was called a **"legal-entity"** (or `legalentity`) in V3 is now called a
**"business-establishment"** in V5 (and "establishment" in v4). The V5 Lucca API also has a resource called **"legal-entity"**, but it represents
a *different* concept — the parent legal company structure. See the [concept mapping](#understanding-the-naming-change)
section below for a detailed explanation.
***
## Understanding the Naming Change
In V3, the `legalentity` resource bundled two concerns into one: the legal company (SIREN in France) **and** the physical
office site (SIRET in France). V4 separated these into `legal-units` and `establishments`.
The V5 Lucca API continues this separation but **renames both resources**:
| V3 | V4 | V5 (Lucca API) | Description |
| :--------------------------------- | :-------------- | :---------------------------- | :------------------------------------------------------------------- |
| `legalentity` | `establishment` | **`business-establishments`** | A physical site, office, or branch — identified by a SIRET in France |
| `legalentity.legalUnit` (embedded) | `legal-unit` | **`legal-entity`** | The legal company structure — identified by a SIREN in France |
If you are migrating from V3 `legalentities`, your target is **`/lucca-api/business-establishments`**, not
`/lucca-api/legal-entities`. The V5 `legal-entity` is the equivalent of the V4 `legal-unit` — the parent company,
not the establishment.
***
## Before You Start: Switch to OAuth 2.0
Regardless of your starting version, the Lucca API requires an **OAuth 2.0 bearer token** in place of the legacy API key.
Request the scope that matches your use case:
| Operation | Required scope |
| :--------------------------- | :--------------------------------- |
| Read business-establishments | `business-establishments.readonly` |
| Read legal-entities | `legal-entities.readonly` |
Additionally, all requests must include the `Api-Version: 2024-11-01` HTTP header.
A single OAuth 2.0 token can carry multiple scopes. Combine all the scopes you need in one token request —
for example: `scope=business-establishments.readonly legal-entities.readonly`.
How to obtain and use OAuth 2.0 bearer tokens with the Lucca authorization server.
How to use the `Api-Version` header to specify the API version.
***
## Pick Your Starting Version
### Quick Start
Request a bearer token with the `business-establishments.readonly` scope (add `legal-entities.readonly` if you also need the parent legal company). See the auth section above.
Include `Api-Version: 2024-11-01` on every request.
Swap `GET /api/v3/legalentities?paging=0,1000` for `GET /lucca-api/business-establishments?include=links,embedded`. Page through results using the cursor token in `links.next`.
Refer to the [field mapping reference](#field-mapping-reference) below for all changes. Remember that V3 `legalentity` maps to V5 `business-establishment`.
V3 embedded the `legalUnit` inside the `legalentity` object. In V5, use the `legalEntity` reference on business-establishments and either request `?include=embedded` to inline the legal-entity, or call `GET /lucca-api/legal-entities/{id}` directly.
The `users`, `currentUsers`, and `roles` fields no longer appear on business-establishment objects.
Use `GET /lucca-api/employees?applicableJobPosition.businessEstablishment.id={id}&status=active&include=totalCount` instead.
***
### What Changes
* **Resource renamed**: V3 `legalentity` → V5 **`business-establishment`**. The V5 `legal-entity` is a *different* resource (the parent company).
* **Authentication**: V3 uses a proprietary API key; the Lucca API uses **OAuth 2.0 bearer tokens**.
* **Versioning**: requests must include the `Api-Version: 2024-11-01` HTTP header.
* **`code` → `remoteId`**: renamed for consistency.
* **`isActive` → `isArchived`**: the boolean is **semantically inverted**.
* **`legalIdentificationNumber` → `taxIdentificationNumber`**: now a structured `{ format, value }` object instead of a plain string.
* **`activityCode` → `industryCode`**: now a structured `{ format, value }` object.
* **`countryID` / `country` moved**: country information is now on the parent `legal-entity`, not on the business-establishment.
* **IDs become strings**: integers in V3 (`1`), strings in V5 (`"1"`).
* **No embedded `users`/`currentUsers`/`roles`**: query the employees endpoint instead.
* **Pagination**: V3 used `?paging={offset},{limit}`; the Lucca API uses cursor tokens. Max **100 items per page** (default 25).
* **`address` restructured**: V3 had `street`/`zipCode`/`city`; V5 uses `addressLines`/`postalCode`/`locality`/`administrativeArea`/`countryCode`.
* **New fields**: `isHeadquarters`, `lastArchivedAt`, `lastUpdatedAt`, `links`.
How cursor-based pagination works in the Lucca API.
***
### Notable Breaking Changes
Pay close attention to these changes — some may cause silent data errors if overlooked.
#### Resource name changed from `legalentity` to `business-establishment`
This is the most critical change. The V3 "legal entity" is **not** the V5 "legal entity" — it maps to the V5 **business-establishment**. Update all endpoint URLs accordingly:
| V3 | V5 |
| :------------------------------- | :-------------------------------------------- |
| `GET /api/v3/legalentities` | `GET /lucca-api/business-establishments` |
| `GET /api/v3/legalentities/{id}` | `GET /lucca-api/business-establishments/{id}` |
#### `isActive` is now `isArchived` (inverted semantics)
| V3 | V5 | Meaning |
| :---------------- | :------------------ | :-------------------------------- |
| `isActive: true` | `isArchived: false` | Establishment is in use |
| `isActive: false` | `isArchived: true` | Establishment is no longer in use |
#### IDs became strings in V5
```json theme={null}
// V3: integer
{ "id": 1 }
// V5: string
{ "id": "1" }
```
The numeric value is preserved. Update any code that stores or compares IDs to treat them as strings.
#### `legalIdentificationNumber` became `taxIdentificationNumber` — a structured object
In V3, `legalIdentificationNumber` was a plain string (e.g. the French SIRET code). In V5, it is a structured object with a format discriminator:
```json theme={null}
// V3 response
{ "legalIdentificationNumber": "44163769100012" }
// V5 response
{
"taxIdentificationNumber": {
"format": "franceSIRET",
"value": "44163769100012"
}
}
```
The `format` field can be `"franceSIRET"`, `"spainNIF"`, or `"other"`.
#### `activityCode` became `industryCode` — a structured object
Same pattern as above:
```json theme={null}
// V3 response (embedded from country)
{ "activityCode": "7022Z" }
// V5 response
{
"industryCode": {
"format": "franceAPE",
"value": "7022Z"
}
}
```
The `format` field can be `"franceAPE"`, `"spainCNAE"`, `"germanyWZ"`, `"usaNAICS"`, or `"other"`.
#### `code` renamed `remoteId`
Map V3 `code` to V5 `remoteId`. Use it for external identifiers.
#### `address` restructured
```json theme={null}
// V3 response
{
"address": {
"street": "10 Place de la Joliette",
"zipCode": "13002",
"city": "Marseille"
}
}
// V5 response
{
"address": {
"addressLines": ["Lucca", "10 Place de la Joliette", "Les Docks Atrium 10.2"],
"postalCode": "13002",
"locality": "Marseille",
"administrativeArea": "Bouches-du-Rhône",
"countryCode": "FRA"
}
}
```
* `street` → `addressLines` (array, supports multiple lines)
* `zipCode` → `postalCode`
* `city` → `locality`
* `administrativeArea` and `countryCode` are new fields
#### Country and currency information moved to the parent `legal-entity`
V3 had `countryID`, `country`, and currency details on the legal entity (now business-establishment). In V5, this information lives on the parent `legal-entity` resource. Use the business-establishment's `legalEntity` reference to retrieve it:
```http theme={null}
GET /lucca-api/legal-entities/{legalEntityId} HTTP/1.1
```
Or include `?include=embedded` on the business-establishment request to get the legal-entity inlined.
#### Embedded `users`, `currentUsers`, and `roles` fields were removed
V3 legal-entity objects included full user and role lists. These are no longer available on business-establishment objects. Query the employees endpoint instead.
***
### Migration by Use Case
#### 1. List All Establishments
```http V3 theme={null}
GET /api/v3/legalentities?paging=0,1000 HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
{
"header": { ... },
"data": {
"items": [
{
"id": 1,
"code": "00128",
"name": "Lucca Paris",
"countryID": 1,
"country": {
"id": 1,
"name": "France"
},
"legalIdentificationNumber": "44163769100012",
"activityCode": "7022Z",
"isActive": true,
"legalUnitId": 3,
"legalUnit": {
"id": 3,
"name": "Lucca SAS",
"code": "LUCCA_FR"
},
"timeZoneId": "Europe/Paris",
"users": [ ... ],
"currentUsers": [ ... ]
}
]
}
}
```
```http V5 theme={null}
GET /lucca-api/business-establishments?include=links,embedded&limit=25 HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"type": "business-establishments",
"url": "https://example.ilucca.net/lucca-api/business-establishments?limit=25",
"totalCount": 28,
"items": [
{
"id": "5",
"type": "business-establishment",
"url": "https://example.ilucca.net/lucca-api/business-establishments/5",
"name": "Lucca Marseille",
"remoteId": "00004",
"legalEntity": {
"id": "123",
"type": "legal-entity",
"url": "https://example.ilucca.net/lucca-api/legal-entities/123"
},
"isHeadquarters": false,
"taxIdentificationNumber": {
"format": "franceSIRET",
"value": "0000000000000"
},
"industryCode": null,
"address": {
"addressLines": ["Lucca", "10 Place de la Joliette", "Les Docks Atrium 10.2"],
"postalCode": "13002",
"locality": "Marseille",
"administrativeArea": "Bouches-du-Rhône",
"countryCode": "FRA"
},
"timezoneId": "Europe/Paris",
"isArchived": false,
"lastArchivedAt": null,
"createdAt": "2024-05-23T13:45:26.0001Z",
"lastUpdatedAt": "2024-05-23T13:45:26.0001Z",
"links": {}
}
],
"links": {
"prev": null,
"next": { "href": "https://example.ilucca.net/lucca-api/business-establishments?page=!sdk87Sdh&limit=25" }
},
"embedded": {
"legal-entity": {
"123": {
"id": "123",
"type": "legal-entity",
"url": "https://example.ilucca.net/lucca-api/legal-entities/123",
"legalName": "Lucca S.A.S.",
"remoteId": "LE:83.2938",
"countryCode": "FRA",
"currencyCode": "EUR",
"taxIdentificationNumber": {
"format": "franceSIREN",
"value": "44163769100128"
},
"industryCode": {
"format": "franceAPE",
"value": "7022Z"
},
"createdAt": "2024-01-01T08:34:23.0001Z",
"lastUpdatedAt": "2024-01-01T08:34:23.0001Z",
"lastArchivedAt": null,
"links": {
"businessEstablishments": {
"href": "https://example.ilucca.net/lucca-api/business-establishments?legalEntity.id=123"
}
}
}
}
}
}
```
Add `?include=embedded` to inline the parent legal-entities. This avoids extra requests when you need the country or legal identifiers that were previously on the V3 legal entity.
#### 2. Retrieve an Establishment by ID
```http V3 theme={null}
GET /api/v3/legalentities/1 HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
{
"header": { ... },
"data": {
"id": 1,
"code": "00128",
"name": "Lucca Paris",
"countryID": 1,
"country": { "id": 1, "name": "France" },
"legalIdentificationNumber": "44163769100012",
"isActive": true,
"legalUnitId": 3,
"legalUnit": { "id": 3, "name": "Lucca SAS" },
"timeZoneId": "Europe/Paris",
"users": [ ... ],
"currentUsers": [ ... ]
}
}
```
```http V5 theme={null}
GET /lucca-api/business-establishments/1?include=embedded HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"id": "1",
"type": "business-establishment",
"url": "https://example.ilucca.net/lucca-api/business-establishments/1",
"name": "Lucca Paris",
"remoteId": "00128",
"legalEntity": {
"id": "3",
"type": "legal-entity",
"url": "https://example.ilucca.net/lucca-api/legal-entities/3"
},
"isHeadquarters": true,
"taxIdentificationNumber": {
"format": "franceSIRET",
"value": "44163769100012"
},
"industryCode": null,
"address": {
"addressLines": ["Lucca", "2 Rue Pillet-Will"],
"postalCode": "75009",
"locality": "Paris",
"administrativeArea": "Île-de-France",
"countryCode": "FRA"
},
"timezoneId": "Europe/Paris",
"isArchived": false,
"lastArchivedAt": null,
"createdAt": "2020-01-01T00:00:00.0001Z",
"lastUpdatedAt": "2025-03-12T09:19:19.0001Z",
"links": {},
"embedded": {
"legal-entity": {
"3": {
"id": "3",
"type": "legal-entity",
"url": "https://example.ilucca.net/lucca-api/legal-entities/3",
"legalName": "Lucca SAS",
"countryCode": "FRA",
"currencyCode": "EUR"
}
}
}
}
```
#### 3. Filter by Active / Archived Status
| Intent | V3 | V5 |
| :------------ | :------------------------------------------------- | :-------------------------------- |
| Active only | `isActive: true` on response (filter client-side) | `?isArchived=false` |
| Inactive only | `isActive: false` on response (filter client-side) | `?isArchived=true` |
| All | All returned by default | Same behavior (omit `isArchived`) |
#### 4. Get the Parent Legal Company
V3 embedded `legalUnit` directly on the legal-entity object. In V5, the parent legal company is a separate resource:
```http theme={null}
GET /lucca-api/legal-entities/3 HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
{
"id": "3",
"type": "legal-entity",
"url": "https://example.ilucca.net/lucca-api/legal-entities/3",
"legalName": "Lucca S.A.S.",
"remoteId": "LE:83.2938",
"isArchived": false,
"countryCode": "FRA",
"currencyCode": "EUR",
"taxIdentificationNumber": {
"format": "franceSIREN",
"value": "44163769100128"
},
"industryCode": {
"format": "franceAPE",
"value": "7022Z"
},
"createdAt": "2024-01-01T08:34:23.0001Z",
"lastUpdatedAt": "2024-01-01T08:34:23.0001Z",
"lastArchivedAt": null,
"links": {
"businessEstablishments": {
"href": "https://example.ilucca.net/lucca-api/business-establishments?legalEntity.id=3"
}
}
}
```
You can also use `?include=embedded` on the business-establishment request to get the parent legal-entity inlined in the `embedded` section — this avoids extra round-trips.
#### 5. Find Employees in an Establishment
V3 embedded `users` and `currentUsers` directly on the legal-entity object. In V5, query the employees endpoint:
```http theme={null}
GET /lucca-api/employees?applicableJobPosition.businessEstablishment.id=1&status=active&include=totalCount HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
```
You need the `employees.readonly` and `job-positions.readonly` scopes to query the employees endpoint.
***
### Field Mapping Reference
| V3 legalentity field | V5 business-establishment field | Notes |
| :------------------------------------------- | :------------------------------ | :----------------------------------------------------------------------------------------------- |
| `id` | `id` | String in V5 (integer in V3) |
| *(new in V5)* | `type` | Always `"business-establishment"` |
| `url` | `url` | Self-link URI |
| `name` | `name` | |
| `code` | `remoteId` | Renamed |
| `countryID` | *(moved)* | Now on the parent legal-entity as `countryCode` (ISO 3166 alpha-3) |
| `country` | *(moved)* | Now on the parent legal-entity |
| `newCountryId` | *(removed)* | Use the parent legal-entity's `countryCode` |
| `newCountry` | *(removed)* | Use the parent legal-entity |
| `legalUnitId` | `legalEntity.id` | Now a typed `{ id, type, url }` reference |
| `legalUnit` | *(embedded)* | Use `?include=embedded` or query `/lucca-api/legal-entities/{id}` |
| `legalIdentificationNumber` | `taxIdentificationNumber` | Now `{ format, value }` — format: `"franceSIRET"`, `"spainNIF"`, or `"other"` |
| *(from country)* `activityCode` | `industryCode` | Now `{ format, value }` on the business-establishment; also available on the parent legal-entity |
| `isActive` | `isArchived` | Semantics **inverted** |
| `timeZoneId` | `timezoneId` | Casing changed |
| `address.street` | `address.addressLines` | Array of strings instead of single string |
| `address.zipCode` | `address.postalCode` | Renamed |
| `address.city` | `address.locality` | Renamed |
| *(new in V5)* | `address.administrativeArea` | Region / state |
| *(new in V5)* | `address.countryCode` | ISO 3166 alpha-3 country code |
| *(new in V5)* | `isHeadquarters` | Whether this is the headquarters of its legal-entity |
| `modifiedOn` | `lastUpdatedAt` | Renamed |
| *(new in V5)* | `createdAt` | |
| *(new in V5)* | `lastArchivedAt` | |
| `users` | *(removed)* | Query the employees endpoint |
| `currentUsers` | *(removed)* | Query the employees endpoint with `status=active` |
| `roles` | *(removed)* | Not available in V5 |
| `modifiedByID` / `modifiedBy` | *(removed)* | Not available in V5 |
| `authorizedOperations` / `authorizedActions` | *(removed)* | Not available in V5 |
### Quick Start
Request a bearer token with the `business-establishments.readonly` scope (add `legal-entities.readonly` if needed). See the auth section above.
Include `Api-Version: 2024-11-01` on every request.
Swap `GET /organization/structure/api/establishments?limit=10` for `GET /lucca-api/business-establishments?include=links`. The response envelope changes: `next` moves from a bare string to `links.next.href`.
Refer to the [field mapping reference](#field-mapping-reference-2) below for all changes.
***
### What Changes
* **Resource renamed**: V4 `establishment` → V5 **`business-establishment`**.
* **Authentication**: V4 uses a proprietary API key; the Lucca API uses **OAuth 2.0 bearer tokens**.
* **Versioning**: requests must include the `Api-Version: 2024-11-01` HTTP header.
* **Endpoint path**: `GET /organization/structure/api/establishments` → `GET /lucca-api/business-establishments`.
* **Response envelope**: `{ items, count, prev, next }` → `{ type, url, items, totalCount, links: { prev, next } }`.
* **`code` → `remoteId`**: renamed for consistency.
* **`legalUnitId` → `legalEntity`**: integer ID replaced by a typed `{ id, type, url }` reference.
* **`legalIdentificationNumber` → `taxIdentificationNumber`**: now a structured `{ format, value }` object.
* **`activityCode` → `industryCode`**: now a structured `{ format, value }` object.
* **`address` restructured**: `street`/`zipCode`/`city` → `addressLines`/`postalCode`/`locality`/`administrativeArea`/`countryCode`.
* **IDs are strings**: integers in V4 (`5`), strings in V5 (`"5"`).
* **`usersCount` removed**: query the employees endpoint with `?include=totalCount`.
* **`calendarId` removed**: not available in V5.
* **`author` / `createdAt` changes**: `author` removed; `createdAt` and `lastUpdatedAt` available.
* **New fields**: `isHeadquarters`, `lastArchivedAt`, `links`.
How cursor-based pagination works in the Lucca API.
***
### Notable Breaking Changes
Pay close attention to these changes — some may cause silent data errors if overlooked.
#### Resource renamed from `establishment` to `business-establishment`
| V4 | V5 |
| :---------------------------------------------------- | :-------------------------------------------- |
| `GET /organization/structure/api/establishments` | `GET /lucca-api/business-establishments` |
| `GET /organization/structure/api/establishments/{id}` | `GET /lucca-api/business-establishments/{id}` |
#### IDs became strings in V5
```json theme={null}
// V4: integer
{ "id": 5 }
// V5: string
{ "id": "5" }
```
The numeric value is preserved. Update any code that stores or compares IDs to treat them as strings.
#### `legalUnitId` replaced with `legalEntity` — a typed reference
V4 exposed the parent legal-unit's numeric ID as `legalUnitId`. In V5, this is a typed reference:
```json theme={null}
// V4 response
{ "legalUnitId": 3 }
// V5 response
{
"legalEntity": {
"id": "3",
"type": "legal-entity",
"url": "https://example.ilucca.net/lucca-api/legal-entities/3"
}
}
```
Note: V4 `legal-unit` is now V5 `legal-entity`.
#### `legalIdentificationNumber` became `taxIdentificationNumber` — a structured object
```json theme={null}
// V4 response
{ "legalIdentificationNumber": "44163769100012" }
// V5 response
{
"taxIdentificationNumber": {
"format": "franceSIRET",
"value": "44163769100012"
}
}
```
The `format` field can be `"franceSIRET"`, `"spainNIF"`, or `"other"`.
#### `activityCode` became `industryCode` — a structured object
```json theme={null}
// V4 response
{ "activityCode": "7022Z" }
// V5 response
{
"industryCode": {
"format": "franceAPE",
"value": "7022Z"
}
}
```
#### `code` renamed `remoteId`
Use `remoteId` for external identifiers.
#### `address` restructured
* `address.street` → `address.addressLines` (array)
* `address.zipCode` → `address.postalCode`
* `address.city` → `address.locality`
* New fields: `address.administrativeArea`, `address.countryCode`
#### Response envelope changed
```json theme={null}
// V4
{
"items": [ ... ],
"count": 28,
"prev": null,
"next": "!cursorToken"
}
// V5
{
"type": "business-establishments",
"url": "https://example.ilucca.net/lucca-api/business-establishments?limit=25",
"items": [ ... ],
"totalCount": 28,
"links": {
"prev": null,
"next": { "href": "https://example.ilucca.net/lucca-api/business-establishments?page=!sdk87Sdh&limit=25" }
}
}
```
In V5, `prev` and `next` are nested under a `links` object and are `{ href }` objects rather than bare strings.
`count` is renamed `totalCount` and only returned when you add `totalCount` to the `?include=` parameter.
***
### Migration by Use Case
#### 1. List All Establishments
```http V4 theme={null}
GET /organization/structure/api/establishments?limit=10 HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
{
"items": [
{
"id": 5,
"name": "Lucca Marseille",
"code": "00004",
"legalUnitId": 123,
"legalIdentificationNumber": "0000000000000",
"activityCode": "7022Z",
"calendarId": 1,
"address": {
"street": "10 Place de la Joliette",
"zipCode": "13002",
"city": "Marseille"
},
"timezoneId": "Europe/Paris",
"usersCount": 42,
"createdAt": "2024-05-23T13:45:26+00:00",
"isArchived": false
}
],
"count": 28,
"prev": null,
"next": "!nextCursorToken"
}
```
```http V5 theme={null}
GET /lucca-api/business-establishments?include=links&limit=25 HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"type": "business-establishments",
"url": "https://example.ilucca.net/lucca-api/business-establishments?limit=25",
"totalCount": 28,
"items": [
{
"id": "5",
"type": "business-establishment",
"url": "https://example.ilucca.net/lucca-api/business-establishments/5",
"name": "Lucca Marseille",
"remoteId": "00004",
"legalEntity": {
"id": "123",
"type": "legal-entity",
"url": "https://example.ilucca.net/lucca-api/legal-entities/123"
},
"isHeadquarters": false,
"taxIdentificationNumber": {
"format": "franceSIRET",
"value": "0000000000000"
},
"industryCode": null,
"address": {
"addressLines": ["Lucca", "10 Place de la Joliette", "Les Docks Atrium 10.2"],
"postalCode": "13002",
"locality": "Marseille",
"administrativeArea": "Bouches-du-Rhône",
"countryCode": "FRA"
},
"timezoneId": "Europe/Paris",
"isArchived": false,
"lastArchivedAt": null,
"createdAt": "2024-05-23T13:45:26.0001Z",
"lastUpdatedAt": "2024-05-23T13:45:26.0001Z",
"links": {}
}
],
"links": {
"prev": null,
"next": { "href": "https://example.ilucca.net/lucca-api/business-establishments?page=!sdk87Sdh&limit=25" }
}
}
```
To paginate in V5, follow the full URL in `links.next.href`. Add `totalCount` to the `include` parameter to also receive the total count: `?include=links,totalCount`.
#### 2. Retrieve an Establishment by ID
```http V4 theme={null}
GET /organization/structure/api/establishments/5 HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
{
"id": 5,
"name": "Lucca Marseille",
"code": "00004",
"legalUnitId": 123,
"legalIdentificationNumber": "0000000000000",
"activityCode": "7022Z",
"calendarId": 1,
"address": {
"street": "10 Place de la Joliette",
"zipCode": "13002",
"city": "Marseille"
},
"timezoneId": "Europe/Paris",
"usersCount": 42,
"createdAt": "2024-05-23T13:45:26+00:00",
"isArchived": false
}
```
```http V5 theme={null}
GET /lucca-api/business-establishments/5 HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
Accept: application/json
{
"id": "5",
"type": "business-establishment",
"url": "https://example.ilucca.net/lucca-api/business-establishments/5",
"name": "Lucca Marseille",
"remoteId": "00004",
"legalEntity": {
"id": "123",
"type": "legal-entity",
"url": "https://example.ilucca.net/lucca-api/legal-entities/123"
},
"isHeadquarters": false,
"taxIdentificationNumber": {
"format": "franceSIRET",
"value": "0000000000000"
},
"industryCode": null,
"address": {
"addressLines": ["Lucca", "10 Place de la Joliette", "Les Docks Atrium 10.2"],
"postalCode": "13002",
"locality": "Marseille",
"administrativeArea": "Bouches-du-Rhône",
"countryCode": "FRA"
},
"timezoneId": "Europe/Paris",
"isArchived": false,
"lastArchivedAt": null,
"createdAt": "2024-05-23T13:45:26.0001Z",
"lastUpdatedAt": "2024-05-23T13:45:26.0001Z",
"links": {}
}
```
#### 3. Filter by Active / Archived Status
| Intent | V4 | V5 |
| :------------ | :----------------------- | :-------------------------------- |
| Active only | `?isArchived=false` | `?isArchived=false` |
| Inactive only | `?isArchived=true` | `?isArchived=true` |
| All | `?isArchived=true,false` | Same behavior (omit `isArchived`) |
#### 4. Filter by Legal-Entity (formerly Legal-Unit)
| V4 | V5 |
| :--------------- | :------------------ |
| `?legalUnitId=3` | `?legalEntity.id=3` |
#### 5. Get the Parent Legal Company
V4 had an embedded `legalUnit` object or a `legalUnitId` integer. In V5, use the `legalEntity` reference on business-establishments:
```http theme={null}
GET /lucca-api/legal-entities/123 HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
```
Or include `?include=embedded` to get the legal-entity inlined in the response.
#### 6. Get the Employee Count
V4 provided `usersCount` directly on the establishment. In V5, query the employees endpoint:
```http theme={null}
GET /lucca-api/employees?applicableJobPosition.businessEstablishment.id=5&status=active&include=totalCount HTTP/1.1
Host: example.ilucca.net
Authorization: Bearer {ACCESS_TOKEN}
Api-Version: 2024-11-01
```
The `totalCount` field in the response is the equivalent of the old `usersCount`.
You need the `employees.readonly` and `job-positions.readonly` scopes to query the employees endpoint, even if you only want the count.
***
### Field Mapping Reference
| V4 establishment field | V5 business-establishment field | Notes |
| :-------------------------- | :-------------------------------- | :-------------------------------------------------------------- |
| `id` | `id` | String in V5 (integer in V4) |
| *(new in V5)* | `type` | Always `"business-establishment"` |
| *(new in V5)* | `url` | Self-link URI |
| `name` | `name` | |
| `code` | `remoteId` | Renamed |
| `legalUnitId` | `legalEntity` | Now a `{ id, type, url }` reference |
| `legalUnit` *(embedded)* | *(use embedded or separate call)* | Use `?include=embedded` or `GET /lucca-api/legal-entities/{id}` |
| `legalIdentificationNumber` | `taxIdentificationNumber` | Now `{ format, value }` |
| `activityCode` | `industryCode` | Now `{ format, value }` |
| `address.street` | `address.addressLines` | Array of strings |
| `address.zipCode` | `address.postalCode` | Renamed |
| `address.city` | `address.locality` | Renamed |
| *(new in V5)* | `address.administrativeArea` | Region / state |
| *(new in V5)* | `address.countryCode` | ISO 3166 alpha-3 |
| `timezoneId` | `timezoneId` | |
| `isArchived` | `isArchived` | Same semantics |
| *(new in V5)* | `isHeadquarters` | Whether this is the legal-entity's headquarters |
| `usersCount` | *(removed)* | Query the employees endpoint with `?include=totalCount` |
| `calendarId` | *(removed)* | Not available in V5 |
| `createdAt` | `createdAt` | |
| *(new in V5)* | `lastUpdatedAt` | |
| *(new in V5)* | `lastArchivedAt` | |
| `author` | *(removed)* | Not available in V5 |
***
## Migrating V4 Legal-Units to V5 Legal-Entities
If you were using V4 `legal-units`, the migration to V5 `legal-entities` is also needed.
| V4 | V5 |
| :-------------------------------------------- | :------------------------------ |
| `GET /organization/structure/api/legal-units` | `GET /lucca-api/legal-entities` |
### What Changes
* **Resource renamed**: V4 `legal-unit` → V5 **`legal-entity`**.
* **`name` → `legalName`**: the field is renamed to clarify it is the legal name of the entity.
* **`code` → `remoteId`**: renamed for consistency.
* **`countryId` (ISO alpha-2) → `countryCode` (ISO alpha-3)**: changed from `"FR"` to `"FRA"`.
* **`legalIdentificationNumber` → `taxIdentificationNumber`**: now a structured `{ format, value }` object.
* **`activityCode` → `industryCode`**: now a structured `{ format, value }` object.
* **`headquartersId` removed**: the headquarters information is now on the business-establishment side (`isHeadquarters: true`).
* **`country` object removed**: replaced by `countryCode` and `currencyCode` top-level fields.
* **IDs are strings**: integers in V4 (`3`), strings in V5 (`"3"`).
* **New fields**: `currencyCode`, `links.businessEstablishments`, `lastArchivedAt`, `lastUpdatedAt`.
### Field Mapping Reference
| V4 legal-unit field | V5 legal-entity field | Notes |
| :-------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------- |
| `id` | `id` | String in V5 (integer in V4) |
| *(new in V5)* | `type` | Always `"legal-entity"` |
| *(new in V5)* | `url` | Self-link URI |
| `name` | `legalName` | Renamed |
| `code` | `remoteId` | Renamed |
| `countryId` (alpha-2) | `countryCode` (alpha-3) | Changed from `"FR"` to `"FRA"` |
| `country` *(object)* | *(removed)* | Replaced by `countryCode` and `currencyCode` |
| *(from country)* | `currencyCode` | Top-level field in V5 (e.g. `"EUR"`) |
| `legalIdentificationNumber` | `taxIdentificationNumber` | Now `{ format, value }` — format: `"franceSIREN"`, `"spainNIF"`, `"germanySN"`, or `"other"` |
| `activityCode` | `industryCode` | Now `{ format, value }` |
| `headquartersId` | *(removed)* | Check `isHeadquarters` on business-establishments instead |
| `isArchived` | `isArchived` | Same semantics |
| `createdAt` | `createdAt` | |
| *(new in V5)* | `lastUpdatedAt` | |
| *(new in V5)* | `lastArchivedAt` | |
| *(new in V5)* | `links.businessEstablishments` | Pre-built link to business-establishments belonging to this entity |
***
## Further Reading
Overview of the Lucca API, versioning, and pagination.
Full reference for the Lucca API business-establishment resource.
Full reference for the Lucca API legal-entity resource.
How to obtain and use OAuth 2.0 bearer tokens.
Cursor-based pagination in the Lucca API, with examples.
Rate and size limits applied to the Lucca API.
# The legal-unit resource
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/legal-units/legal-unit
organization-v3
# List legal-units
Source: https://developers.luccasoftware.com/api-reference/legacy/organization/legal-units/list-legal-units
/openapi-specs/organization-v3.yaml get /organization/structure/api/legal-units
Retrieve a paginated list of legal-units.
# The application resource
Source: https://developers.luccasoftware.com/api-reference/legacy/recruitment/applications/application
Every successful creation triggers an automated email to the candidate, consistent with the standard Lucca form application workflow.
# Create a new application for a job opportunity
Source: https://developers.luccasoftware.com/api-reference/legacy/recruitment/applications/post-application
/openapi-specs/recruitment.yaml post /recruitment/api/public/applications
This route allows creating a new application for a job opportunity,
given candidate information.
Usable only with an API key. It is designed for custom career sites
that need to push candidate data directly into Lucca Recruitment.
Every successful creation triggers an automated email to the candidate, consistent with the standard Lucca form application workflow.
# List active job offers
Source: https://developers.luccasoftware.com/api-reference/legacy/recruitment/job-offers/get-job-offers
/openapi-specs/recruitment-world.yaml get /xml/{customer_subdomain}/lucca.xml
Returns an XML file containing company metadata and a list of all currently open job positions.
# Get Started With Job-Offers
Source: https://developers.luccasoftware.com/api-reference/legacy/recruitment/job-offers/job-offer
The Job-Offers API endpoint is global and publicly accessible, allowing third-party
job boards and aggregators to retrieve active job offers from Lucca customers who
have opted in to share their job data with external platforms.
As a result:
* base URL is "[https://jobs.world.luccasoftware.com](https://jobs.world.luccasoftware.com)" (rather than the usual "https\://.ilucca.net");
* the tenant identity is provided as a path parameter `{customer_subdomain}`;
* it does not require authentication.
Bear in mind some XML elements may contain HTML content.This API endpoint returns ALL active job-offers for the tenant, without paging.
```http theme={null}
GET /xml/{customer_subdomain}/lucca.xml HTTPS/2
Host: jobs.world.luccasoftware.com
Accept: application/xml
```
```xml theme={null}
Acme CorpAcme Corp designs and manufactures innovative solutions for businesses worldwide.101Senior Software Engineer<p>🚀 Join our engineering team to build scalable backend services and APIs!</p>
<ul>
<li>🛠️ Design and develop high-performance REST APIs</li>
<li>☁️ Deploy and maintain services on cloud platforms</li>
<li>🤝 Collaborate with product and design teams in an agile environment</li>
<li>🔍 Participate in code reviews and mentor junior developers</li>
</ul><p>💼 We are looking for an experienced engineer with:</p>
<ul>
<li>✅ 5+ years of experience in backend development</li>
<li>✅ Proficiency in Java or C#</li>
<li>✅ Strong knowledge of REST APIs and cloud platforms</li>
<li>⭐ Bonus: experience with Kubernetes or Terraform</li>
</ul>Paris, FrancePermanent55K - 70K €https://acme.ilucca.net/jobs/101/apply102Marketing Intern<p>📣 Support the marketing team in all things content and campaigns!</p>
<ul>
<li>✍️ Assist in writing blog posts and social media content</li>
<li>📊 Analyze campaign performance and prepare reports</li>
<li>🎨 Collaborate with the design team on visual assets</li>
<li>📅 Help plan and coordinate marketing events</li>
</ul><p>🎓 We are looking for a motivated intern who has:</p>
<ul>
<li>✅ Currently enrolled in a Marketing or Communications degree</li>
<li>✅ Good written communication skills in French and English</li>
<li>⭐ Bonus: familiarity with social media tools (Hootsuite, Buffer…)</li>
</ul>Lyon, FranceInternship800 - 1000 € / monthhttps://acme.ilucca.net/jobs/102/apply
```
# Lucca Developers Hub
Source: https://developers.luccasoftware.com/home
Lucca Developers Hub
Connect Lucca With Anything
Effortlessly integrate Lucca with your existing apps and workflows
thanks to our APIs and further automate and supercharge your HR
processes.
Browse our marketplace of ready-to-use integrations. No sweat.
This documentation contains guides that describe how to achieve common integration use-cases.
Browse the API reference to build your integration:
Our versioned public API with a stable contract, OAuth 2.0 authentication, and long-term support. Recommended for new integrations, but it doesn't cover all our resources yet. Some endpoints are still in beta.
Our v3 / v4 APIs, feature-complete and used in production by thousands of customers every day.
You can freely toggle between the [Previous APIs](./api-reference/legacy/) and the [Lucca API](./api-reference/latest/) with the selector on the top left of this website (next to the Lucca logo).
# Delete Document
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/documents/delete-document
/openapi-specs/lucca-invoices.yaml delete /cleemy-procurement/api/documents/{id}
Delete a document.
# The document resource
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/documents/document
lucca-invoices
# Get Document
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/documents/get-document
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/api/documents/{id}
Retrieve a document by its ID.
# Update Document
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/documents/put-document
/openapi-specs/lucca-invoices.yaml put /cleemy-procurement/api/documents/{id}
Update a document's information (e.g. accounting fields, amounts, supplier details) before booking it.
**Warning**: This is a `PUT` request — the entire resource must be provided. Any field omitted from the payload may be reset to its default value.
# Create an approved order
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/guides/create-approved-order
Learn how to create an approved Order in Lucca Invoices
## Steps
Creating an approved Order is done through three distinct calls:
The order will eventually be created once the approval workflow succeeds, which corresponds to approving the purchase request.
***
## 1. Create a new Purchase
```http Create a Purchase theme={null}
POST /cleemy-procurement/api/purchases HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"ownerId": 35, // Identifier of the user this purchase belongs to.
"natureId": 1, // Identifier of the nature of this purchase.
"costCenterId": 3,// Cost center identifier
"establishmentId": 1, // Identifier of the business-establishment. Usually the one the owner of this purchase works in.
"departmentId": 14, // Identifier of the department. Usually the one the owner of this purchase works in.
"supplier": {
"id": 6,
"name": "Amazon",
"territoryId": "FR"
},
"title": "Purchase entry test",
"axisSectionIds": [361] // Optional, depends on client configuration.
}
```
Success is indicated by a `201` response status code. If so, the response contains a `purchaseId` which is the identifier of the created Purchase object.
File upload is done via the upload API of our file management system (lucca-files). Please [refer to the dedicated documentation](../../files/post-upload).
***
## 2. Create a Commitment
Pleas note a Commitment can be of type "one-time" (i.e. `OneTimeCommitment`) or "recurring" (i.e. `SubscriptionCommitment`). In which case the payload changes slightly.
```http Create a OneTimeCommitment theme={null}
POST /cleemy-procurement/api/purchases/{purchaseId}/commitments HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"costDate": "2025-04-14", // delivery date
"invoiceDate": "2025-04-14", // invoice date
"amountIncludingTaxes": { // amount, with an excluding taxes part and taxes (array by rate)
"excludingTaxes": {
"value": 120,
"currency": "EUR"
},
"taxes": []
},
"type": "OneTimeCommitment", // purchase type, detailed below.
"attachmentIds": [], // file attached to the purchase request detailed in the annex
"state": "Created",
"comment": "Comment",
"paymentMethod": null // payment method: can be left empty.
}
```
```http Create a SubscriptionCommitment (recurring) theme={null}
POST /cleemy-procurement/api/purchases/{purchaseId}/commitment HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"startDate": "2025-04-14", // subscription start date
"firstInvoiceDate": "2025-04-14", // first invoice date
"invoicingFrequency": { // invoicing frequency
"value": 1,
"unit": "Month" // possible values: Day, Week, Month, Year
},
"subscriptionDuration": {
"value": 12,
"unit": "Month" // possible values: Day, Week, Month, Year
},
"contractualObligation": null, // can be left empty
"terminationNotice": null, // can be left empty
"amountIncludingTaxesPerPeriod": {
"excludingTaxes": {
"value": 1000,
"currency": "EUR"
},
"taxes": []
},
"type": "SubscriptionCommitment",
"attachmentIds": [],
"state": "Created",
"comment": "Comment",
"paymentMethod": null
}
```
You may retrieve a list of all VAT rates applicable to a given territory at a given date via the tax management service. You will need it to set up the correct VAT rate when creating a Commitment.
```http List taxe rates theme={null}
GET /cleemy-procurement/taxes/api/rates?date={date}&territoryId=FR HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
```
Success is indicated by a `201` response status code. If so, the response contains a `commitmentId` which is the identifier of the created Commitment object.
***
## 3. Trigger the approval workflow
```http Trigger workflow theme={null}
POST /cleemy-procurement/api/commitments/{commitmentId}/requests HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Length: 0
```
The request body is expected to be empty.
The system will configure auto-approval of requests so that the order is automatically created at the end of the workflow.
***
## Addendum
You may need to cancel or delete a Purchase that was previously created.
Please note that strictly speaking, a Purchase cannot be deleted (HTTP DELETE requests are not supported), but it can be "closed". A Purchase may only be closed once all invoices related to it have been approved and paid (or just cancelled).
```http Cancel a purchase request theme={null}
POST /cleemy-procurement/api/requests/{requestId}/cancellations HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"comment": "Comment"
}
```
```http Close a purchase (eq. "delete") theme={null}
POST /cleemy-procurement/api/purchases/{purchaseId}/closing HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"comment": "Comment",
"date": "2025-04-20"
}
```
# Import cost-centers
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/guides/import-cost-centers
Use our API dedicated to import cost centers within Lucca Invoices
## Description
If you want to create several cost centers through our API, you can use an endpoint made for it. It natively manages potential errors you might meet while making the import.
Moreover, it allows you to choose between two solutions if an error is detected :
* skip the process and import no data
* import only the data correct
## API call
```http Import a list of cost-centers theme={null}
POST /cleemy-procurement/services/imports/costcenters HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
{
"mode": "AllOrNothing",
"items": [
{
"name": "string",
"code": "string",
"ownerId": 0, // userId Lucca
"state": "Enabled", // possible values : enabled or disabled. Or enabled by default.
"establishmentIds": [
0 // establishmentId Lucca
]
}
]
}
```
Success is indicated by a `200` response status code. If so, the response contains a `purchaseId` which is the identifier of the created Purchase object.
Two modes are possible :
* `AllOrNothing` : if an error is detected, skip the whole process and import no data.
* `SkipInvalidItems` : if an error is detected, skip only the invalid items and import the valid ones.
# Process an invoice automatically
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/guides/process-invoice-automatically
Retrieve inbox documents, update their information, and book them via the API
Automatically processing an invoice requires three steps: retrieving inbox documents, updating the invoice information,
and booking it.
The document inbox holds invoice documents that have been uploaded or received but not yet validated and booked into accounting. Booking an invoice means marking it as validated and ready for accounting.
Use the `GET /cleemy-procurement/services/inbox` endpoint to list documents in the inbox.
Results are paginated with a maximum of **1000 items per page**. To retrieve all documents, proceed in two passes:
**1. Get the total count** by requesting only the `count` field along with your filters:
```http Get total count theme={null}
GET /cleemy-procurement/services/inbox?fields.root=count&{your_filters} HTTP/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
```
The list of supported filters can be found in the [API Reference](/api-reference/legacy/cleemy-invoices/inbox/inbox-document).
**2. Fetch all pages** by looping from page 1 until the number of items retrieved matches the total count:
```http Fetch a page of results theme={null}
GET /cleemy-procurement/services/inbox?limit=100&page={pageNumber}&{your_filters} HTTP/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
```
Use the **same filters** on both calls so the count matches the actual results.
For more details, see the [Inbox-document resource](/api-reference/legacy/cleemy-invoices/inbox/inbox-document) page.
Before booking, you may need to update the invoice data (e.g., accounting fields, amounts, or supplier info).
For this, use `PUT /cleemy-procurement/services/inbox-documents/{id}`:
```http Update an inbox document theme={null}
PUT /cleemy-procurement/services/inbox-documents/{id} HTTP/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Content-Type: application/json
Accept: application/json
{
"field": "updatedValue"
}
```
This is a `PUT` request — you must send the **complete** resource representation,
not just the fields you want to change.
Once the invoice information is complete, book it by calling `POST /cleemy-procurement/services/inbox-documents/{id}/book`.
**No request body is needed.**
```http Book an inbox document theme={null}
POST /cleemy-procurement/services/inbox-documents/{id}/book HTTP/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
Content-Length: 0
```
# Retrieve processed invoices
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/guides/retrieve-processed-invoices
Learn how to retrieve already processed invoices from Lucca Invoices to sync them with your ERP
Retrieving all invoices imported into Lucca Invoices to add them to the ERP requires some precautions, since invoices can be deleted.
It is therefore recommended to only sync approved invoices, which can no longer be deleted.
For this, the API endpoint to use is the `booked-documents`.
```http List booked-documents theme={null}
GET /cleemy-procurement/booked-documents?invoice.conformityStates=Confirmed&creditNote.conformityStates=Confirmed&createdAt.from={lastImportDate}&limit=0&page=1&fields.root=count HTTPS/2
Host: example.ilucca.net
Authorization: lucca application={API_KEY}
Accept: application/json
```
Where:
* `?createdAt.from=` filters out results to only return the ones that were created since your last sync attempt.
* `?invoice.conformityStates` and `creditNote.conformityStates=Confirmed` filter out results to only return approved invoices.
This API is paginated, and the maximum number of items retrievable per page is 1000. The `?fields.root=count` query parameter will request the server to return the total number of items across all pages.Information related to the order is present in the `purchase` node. You might need to make a specific call to the purchase API to get the data you need.
Please note that this API retrieves all documents accounted for in the system, invoices and credit notes. Hence the `creditNote.conformityState` query parameter in the API request.
Regarding amounts:
* `expectedAmount`: theoretically expected amount (the order amount).
* `costAmount`: charge amount (case where VAT would not be recoverable).
* `amountIncludingTaxes`: object grouping the excluding taxes amount and VAT. **This is the amount I recommend you use.**
Amounts include both a `counter` part (**converted amount**) and a `base` (**original currency amount**) in order to support foreign currencies.
# List Inbox Documents
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/inbox/inbox-document/get-inbox-documents
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/services/inbox
List documents waiting to be processed in the inbox.
# Book an Inbox Document
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/inbox/inbox-document/post-inbox-document-booking
/openapi-specs/lucca-invoices.yaml post /cleemy-procurement/services/inbox-documents/{id}/book
Book (validate) a document from the inbox. No request body is needed.
# Update Inbox Document
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/inbox/inbox-document/put-inbox-document
/openapi-specs/lucca-invoices.yaml put /cleemy-procurement/services/inbox-documents/{id}
Update an inbox document and optionally attach or create a purchase.
# List V.A.T. rates
Source: https://developers.luccasoftware.com/api-reference/legacy/cleemy-invoices/purchases/get-vat-rates
/openapi-specs/lucca-invoices.yaml get /cleemy-procurement/taxes/api/rates
List V.A.T. rates.
# Assign a Working Time Arrangement to an Employee
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/employee-assignments/assign-working-time-arrangement-to-employee
/openapi-specs/schedule.yaml post /schedule/api/employee-assignments
You may not benefit from this feature. Working-time-arrangements are the
replacement for the workcycles. If this feature has not been deployed on
your environment, please contact our support.
Assigns a new working time arrangement to a single employee, effective from the specified start date (assignmentStart).
This operation will:
1. End the current assignment with an end date set to the eve of `assignmentStart`.
2. Create a work-contract amendment for the employee at `assignmentStart`, that gives them the new working time arrangement.
# The employee-assignment resource
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/employee-assignments/employee-assignment
An employee-assignment represents the assignment of a working time arrangement to an employee over a specific period.
The end date of the period can be open-ended (null) to indicate an ongoing assignment.
In practice, assignments are handled via employee work-contracts. As a result, a new assignment will result in the creation of a work-contract amendment for the employee.
# List Employee Assignments
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/employee-assignments/list-employee-assignments
/openapi-specs/schedule.yaml get /schedule/api/employee-assignments
You may not benefit from this feature. Working-time-arrangements are the
replacement for the workcycles. If this feature has not been deployed on
your environment, please contact our support.
Returns a paginated list of employee-assignments. An employee-assignment represents the assignment of a working-time-arrangement to an employee over a given period `startsOn` - `endsOn`.
# Get Started with the Schedule API
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/get-started
The Schedule API allows you to manage your employees' **theoretical working schedules**: how many hours or days
they are expected to work, on which days, and at what times.
This is the foundation upon which other Lucca modules (Absences, Timesheet) rely to compute leave balances,
overtime, and time-tracking expectations.
***
## Key concepts
An employee's effective schedule (i.e. `UserDate`) on any given day is computed by layering three things, in order:
```mermaid theme={null}
flowchart TD
A["Base schedule\n(Workcycle or WTA)"] -->|+| B["Public Holidays\n(calendar)"]
B -->|+| C["One-time overrides\n(Exceptions or ShiftOverrides)"]
C -->|=| D(["UserDate"])
```
1. **Base recurring schedule** — defines the default working hours for each day of the week (possibly alternating odd/even weeks).
2. **Public Holidays** — days marked as non-working in the employee's public-holidays calendar override the base schedule.
3. **One-time overrides** — punctual modifications for a specific employee on a specific day (e.g. "exceptionally, this employee does not work next Thursday").
The **final, computed result** for a given employee on a given day is exposed via the [UserDate](./userdates/userdate) resource.
***
## Two co-existing models
The Schedule API is undergoing a major refactor. Depending on whether the **Working Time Arrangements** feature has been
enabled on your Lucca environment, you will use one of two models (more below). Both produce the same end result ([UserDates](./userdates/userdate)),
but they differ in how the base schedule and overrides are represented.
If you are unsure which model applies to your environment, contact [Lucca Support](https://support.lucca.fr).
New integrations should target the **New Model** whenever possible.
### Old Model (Workcycles)
| Resource | Role |
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| **Workcycle** | A recurring weekly pattern of working hours, assigned to employees via their V3 User's `userWorkcycles` property. |
| [**Workcycle Exception**](./workcycle-exceptions/workcycle-exception) | A one-time override of an employee's working hours on a specific half-day. |
A workcycle is a theoretical **recurring** schedule assigned to an employee. It defines their working days and hours with a weekly (or bi-weekly) pattern. It is assigned to employees via the `userWorkcycles` property of the V3 User.
A workcycle exception overrides the employee's planned working hours on a specific **half-day** (AM or PM). Use it to add, remove, or change scheduled hours for one employee on one date.
### New Model (Working Time Arrangements)
| Resource | Role |
| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| [**Working Time Arrangement**](./working-time-arrangements/working-time-arrangement) (WTA) | Replaces the Workcycle. Defines recurring working hours, contractual time, full-time equivalent, and associated rules. |
| [**Collective Schedule**](./working-time-arrangements/collective-schedule) | The weekly hours pattern within a Working Time Arrangement (WTA) over a given date-range. A WTA can have successive collective schedules over time. |
| [**Employee Assignment**](./employee-assignments/employee-assignment) | Links a Working Time Arrangement to an employee for a given period (via a work-contract amendment). |
| [**Shift Override**](./shift-overrides/shift-override) | Replaces the Workcycle Exception. A one-time modification to an employee's schedule on a specific day. |
The successor to the Workcycle. Defines recurring working hours, the contractual working time duration, the full-time equivalent, and the public-holidays calendar for a population of employees.
Describes the actual weekly pattern of a Working Time Arrangement (e.g. "Monday to Friday, 09:00-17:00") over a given date-range. Can be a single repeating week or alternate between odd and even weeks.
Assigns a Working Time Arrangement to an employee starting on a given date. Creating an assignment generates a work-contract amendment automatically.
A one-time modification to an employee's regular schedule. Can mark a day as worked, off, part-time, or weekly rest, and optionally change the duration or start time.
***
## The UserDate: reading the effective schedule
Regardless of which model you use, the **[UserDate](./userdates/userdate)** resource gives you the **final, computed schedule**
for one or more employees over a date range. It aggregates:
* **Theoretical hours** from the base schedule (Workcycle or WTA).
* **Public Holiday** overrides from the employee's holidays calendar.
* **Exception / Shift Override** modifications.
* **Leaves** from Lucca Absences (if applicable).
* **Time entries** from Lucca Timesheet (if applicable).
Each UserDate exposes:
| Field | Meaning |
| -------------- | ------------------------------------------------------------------------------------------------- |
| `dtt` | **Theoretical working time** — what the employee should work on that day. |
| `drt` | **Actual working time** — what the employee actually worked (defaults to `dtt` if no time entry). |
| `draj` | **Leave duration** — time absent via leaves. |
| `isHoliday` | Whether the day is a public holiday. |
| `isWeeklyRest` | Whether the day is a weekly rest day (e.g. Sunday). |
| `items[]` | Detailed breakdown of all events for that day. |
For performance, always scope UserDate requests to a limited set of employees and a short date range.
As a rule of thumb, do not request more than \~10 employees over a single month.
***
## Understanding time units
Every schedule resource operates in one of three **units**. The unit is defined at the Workcycle or Working Time Arrangement level and determines how durations are expressed:
| Unit | Meaning | Example |
| -------- | ----------------------------------------------------------------------- | ------------------------------ |
| **Day** | Durations are expressed as fractions of a day. `12:00:00` = half a day. | "I worked half a day" |
| **Hour** | Durations are expressed in hours. `03:30:00` = 3 hours and 30 minutes. | "I worked 3h30" |
| **Time** | Durations are expressed in hours **with a start time**. | "I worked from 09:00 to 12:30" |
When creating Workcycle Exceptions or Shift Overrides, the unit you specify **must match** the unit of the
employee's applicable base schedule. If the employee's schedule is in "hours", you cannot create an exception in "days".
***
## Typical integration scenarios
### Reading an employee's schedule
To know what an employee is expected to work over a period, query the [UserDate endpoint](./userdates/userdate):
```http Get UserDates theme={null}
GET /api/v3/userdates?ownerId=42&date=between,2026-06-01,2026-06-30&fields=ownerId,date,dtt,isHoliday,isWeeklyRest,items[startsAt,duration,type,timeline] HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={api_key}
```
### Overriding an employee's schedule for one day
**Old Model** — Create a [Workcycle Exception](./workcycle-exceptions/workcycle-exception):
```http Create Exception theme={null}
POST /api/v3/workcycleExceptions HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={api_key}
Content-Type: application/json
{
"ownerId": 42,
"startsAt": "2026-06-15T00:00:00",
"isAm": true,
"duration": "00:00:00",
"unit": 1
}
```
This tells the system: "Employee #42 will not work on the morning of June 15th" (in hours unit).
**New Model** — Replace [Shift Overrides](./shift-overrides/shift-override) for the employee on that date:
```http Override theme={null}
PUT /schedule/api/shift-overrides?employee.id=42&startsAt.between=2026-06-15--2026-06-15 HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={api_key}
Content-Type: application/json
[
{
"employeeId": 42,
"position": "firstHalf",
"startsAt": "2026-06-15T00:00:00",
"duration": { "unit": "hours", "iso": "PT0H" },
"nature": "off"
}
]
```
The Shift Override PUT endpoint uses **replace semantics**: all existing overrides for the employee within the
specified date range are removed and replaced with the ones you provide. To clear all overrides for a period,
send an empty array `[]`.
### Assigning a new schedule to an employee (New Model only)
To change the Working Time Arrangement assigned to an employee:
```http Assign theme={null}
POST /schedule/api/employee-assignments HTTP/1.1
Host: example.ilucca.net
Authorization: lucca application={api_key}
Content-Type: application/json
{
"employeeId": 42,
"workingTimeArrangementId": 7,
"startsOn": "2026-07-01"
}
```
This will:
End the employee's current assignment on June 30th (the eve of the new start date).
Create a work-contract amendment effective July 1st with the new Working Time Arrangement.
***
## Model comparison at a glance
| Aspect | Old Model | New Model |
| :------------------- | :------------------------- | :---------------------------------------------------- |
| Base schedule | Workcycle | Working Time Arrangement + Collective Schedule |
| Assignment mechanism | V3 User `userWorkcycles` | Employee Assignment (creates work-contract amendment) |
| One-time override | Workcycle Exception | Shift Override |
| Override API style | POST individual exceptions | PUT replaces all overrides in a date range |
| Computed result | UserDate | UserDate |
# List ShiftOverrides.
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/shift-overrides/list-shift-overrides
/openapi-specs/schedule.yaml get /schedule/api/shift-overrides
Retrieve a paginated list of ShiftOverrides that satisfy the given query filters.
A ShiftOverride represents a one-time modification to an employee's regular work schedule
for a specific period. It overrides the employee's normal working pattern (defined by their
work cycle) for the specified time range.
# Replace ShiftOverrides for an employee.
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/shift-overrides/replace-shift-overrides
/openapi-specs/schedule.yaml put /schedule/api/shift-overrides
Replace all ShiftOverrides for a given employee within the specified date range.
This endpoint uses a PUT semantic: all existing ShiftOverrides for the employee within
the `startsAt.between` range will be removed and replaced by the ones provided in the
request body. If none exist, then the provided ShiftOverrides will simply be created.Both `employee.id` and `startsAt.between` query parameters are required.
# The shift-override resource
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/shift-overrides/shift-override
schedule
# List UserDates.
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/userdates/list-userdates
/openapi-specs/schedule.yaml get /api/v3/userdates
Retrieve a list of UserDates.
### Performance
In order to keep satisfactory response times and performance, please filter the request on a limited set of OwnerId[] and reduce the period `date=between,{start},{end}`.
As a rule of thumb, do not request for more than ~10 owners over a whole month.
### Format
The `date` query parameter should match the form `?date=between,{start},{includedEnd}`.
# The user-date resource
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/userdates/userdate
schedule
# Create (or update) a WorkCycleException.
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/workcycle-exceptions/create-workcycle-exception
/openapi-specs/schedule.yaml post /api/v3/workcycleExceptions
An employee can only have a single `WorkCycleException` on any given
half-day (indicated by the `startsAt` date and the `isAm` boolean).
As a result, creating a new `WorkCycleException` via a POST request
may result in the update of the existing one.
# Delete a WorkCycleException.
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/workcycle-exceptions/delete-workcycle-exception
/openapi-specs/schedule.yaml delete /api/v3/workcycleExceptions/{id}
The `WorkCycleException`, once deleted, stays available in the API.
Deleting a `WorkCycleException` means marking it as deleted by setting its `deletedAt` property to the deletion date. This allows us to keep track of deletions and to provide the possibility to filter out deleted `WorkCycleExceptions` from list responses.
In order to filter out the deleted `WorkCycleException`, you may use the `deletedAt`
query parameter:
```http
GET /api/v3/workcycleexceptions?deletedAt=null HTTPS/1.1
Host: example.ilucca.net
Authorization: lucca application=XXXX
```
# Retrieve a WorkCycleException.
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/workcycle-exceptions/get-workcycle-exception
/openapi-specs/schedule.yaml get /api/v3/workcycleExceptions/{id}
Retrieve a single WorkCycleException by its ID.
# List WorkCycleExceptions.
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/workcycle-exceptions/list-workcycle-exceptions
/openapi-specs/schedule.yaml get /api/v3/workcycleExceptions
Retrieve a paginated list of WorkCycleExceptions that satisfy the
given query filters.
# The workcycle-exception resource
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/workcycle-exceptions/workcycle-exception
schedule
# The collective-schedule resource
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/working-time-arrangements/collective-schedule
schedule
# Get a Collective Schedule from a Working Time Arrangement by id.
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/working-time-arrangements/get-collective-schedule
/openapi-specs/schedule.yaml get /schedule/api/working-time-arrangements/{workingTimeArrangementId}/collective-schedules/{collectiveScheduleId}
**This is a beta feature.**
You may not benefit from this feature. Working-time-arrangements are the
replacement for the workcycles. If this feature has not been deployed on
your environment, please contact our support.
Get a Collective Schedule of a Working Time Arrangement by id
# Get a Working Time Arrangement by id.
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/working-time-arrangements/get-working-time-arrangement
/openapi-specs/schedule.yaml get /schedule/api/working-time-arrangements/{workingTimeArrangementId}
**This is a beta feature.**
You may not benefit from this feature. Working-time-arrangements are the
replacement for the workcycles. If this feature has not been deployed on
your environment, please contact our support.
Get a Working Time Arrangement by id
# List Collective Schedules from a Working Time Arrangement
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/working-time-arrangements/list-collective-schedules
/openapi-specs/schedule.yaml get /schedule/api/working-time-arrangements/{workingTimeArrangementId}/collective-schedules
**This is a beta feature.**
You may not benefit from this feature. Working-time-arrangements are the
replacement for the workcycles. If this feature has not been deployed on
your environment, please contact our support.
Get the list of Collective Schedules from a Working Time Arrangement by id
# List Working Time Arrangements.
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/working-time-arrangements/list-working-time-arrangements
/openapi-specs/schedule.yaml get /schedule/api/working-time-arrangements
You may not benefit from this feature. Working-time-arrangements are the
replacement for the workcycles. If this feature has not been deployed on
your environment, please contact our support.
List of Working Time Arrangements.
# The working-time-arrangement resource
Source: https://developers.luccasoftware.com/api-reference/legacy/schedule/working-time-arrangements/working-time-arrangement
schedule
# Import vs. Replace entitlements
Source: https://developers.luccasoftware.com/api-reference/legacy/timmi-absences/guides/entitlements-imports
Understand the differences between Import entitlements and Replace entitlements, their business and technical impact, and how to call each endpoint.
## Introduction
Lucca Absences provides two endpoints for managing leave entitlements via file import:
**Adds** the file values to existing balances
**Adjusts** balances so that entitlement totals match the file values exactly.
Choosing the right endpoint is critical: they have **very** different consequences on employee leave balances.
## Key differences
| Aspect | **Import** entitlements | **Replace** entitlements |
| :------------------- | :----------------------------------------------------------------- | :--------------------------------------------------------------------------------------- |
| **Behavior** | File value is **added** to the current balance. | File value **becomes** the target total; the system computes and applies the difference. |
| **Idempotency** | Not idempotent — importing the same file twice doubles the credit. | Effectively idempotent — re-importing produces zero adjustment. |
| **Accounting entry** | Credit entry (code `Repor`). | Adjustment entry, positive or negative (code `Auto`). |
| **Typical use case** | Periodic top-up (e.g., "grant 2.08 additional days this month"). | Full sync with an external system (e.g., "this employee should have 25 days total"). |
With **Import entitlements**, accidentally re-importing a file will double-credit the accounts. Always call with `?simulate=true` query parameter first to preview the result.
## File format (both endpoints)
Accepted formats:
* **CSV** — UTF-8 encoding, semicolon (`;`) separator
* **XLSX**
**Required columns:**
| Column | Description |
| :--------------------------- | :--------------------------- |
| `LegalEntityCode` | Employee's legal entity code |
| `EmployeeNumber` | Employee's payroll number |
| `LastName` | Last name |
| `FirstName` | First name |
| Leave account number or name | Value for each leave account |
**Example CSV:**
```csv theme={null}
LegalEntityCode;EmployeeNumber;LastName;FirstName;CP 2024/2025;RTT 2025
001;10045;Doe;Jane;25;10
001;10046;Smith;John;20;8
```
A template file can be downloaded from the Lucca Absences UI: **Credit / Debit > Imports**.
## Query parameters (both endpoints)
| Parameter Name | Type | Default | Description |
| :------------- | :------ | :------ | :------------------------------------------------------------------------------------------------------------------------------------------ |
| `strict` | boolean | `false` | When `true`, if any line has errors, **nothing** is imported. When `false`, errored lines are skipped but others are still imported . |
| `simulate` | boolean | `false` | When `true`, returns a preview of the import result without persisting any data. Useful for testing out the file before committing changes. |
## Response format (both endpoints)
```json theme={null}
{
"globalErrors": [],
"lineErrors": [],
"successLinesCount": 2
}
```
| Field | Description |
| :------------------ | :------------------------------------------------------------------------------------------ |
| `globalErrors` | Fatal errors preventing the entire import (e.g., `FileEmpty`, `ColumnsFormatNotSupported`). |
| `lineErrors` | Per-line errors (e.g., `EmployeeNumberNotExist`, `AccountNotFound`). |
| `successLinesCount` | Number of lines successfully processed. |
Always run with `simulate=true` first to validate your file. All imports are recorded in the history and can be reviewed in the Lucca Absences UI.
## Import entitlements
Each call **adds** the file values to the current balance. A file value of `2.08` increases the balance by 2.08 days — regardless of the current total.
### Form-data fields
| Field | Required | Description |
| :-------------- | :------- | :---------------------------------------------- |
| `File` | Yes | The CSV or XLSX import file. |
| `Description` | Yes | Label visible to users in the account details. |
| `ReferenceDate` | Yes | Reference date in `yyyy-MM-ddTHH:mm:ss` format. |
### Example request
```http theme={null}
POST /timmi-absences/api/public/services/v1.0/leaveEntitlementsImport?strict=false&simulate=false HTTP/1.1
Host: {yourDomain}
Authorization: lucca application={apiKey}
Content-Type: multipart/form-data; boundary=----Boundary
------Boundary
Content-Disposition: form-data; name="File"; filename="entitlements.csv"
Content-Type: application/octet-stream
LegalEntityCode;EmployeeNumber;LastName;FirstName;CP N;RTT
001;10045;Doe;Jane;2.08;0.83
------Boundary
Content-Disposition: form-data; name="Description"
Monthly entitlements credit - April 2026
------Boundary
Content-Disposition: form-data; name="ReferenceDate"
2026-04-01T00:00:00
------Boundary--
```
## Replace entitlements
Each call **sets** the entitlement total to the file value. The system computes `file_value − current_entitlement_total` and creates an adjustment entry for the difference (positive or negative).
The "current entitlement total" is computed only from the entry types specified by the `EntryTypes` parameter. By default: `AutoAccruals`, `ManualAccruals`, `Regularizations`.
### Supported entry types
| Value | Description | Accounting codes included |
| :------------------- | :------------------------------------ | :--------------------------------------- |
| `AutoAccruals` | Automatic periodic accruals | `Auto`, `Rounding` |
| `ManualAccruals` | Manual adjustments by administrators | `Odsa`, `SemiAuto`, `Manuel`, `Transfer` |
| `Regularizations` | Regularization entries | `Regul`, `RegulDeparture` |
| `EntitlementsImport` | Previous entitlements import entries | `Repor` |
| `Seniority` | Seniority-based entitlements | `Seniority` |
| `Fractionnement` | Fractionnement (French-specific rule) | `Fract` |
| `TimeSavings` | Time savings account entries | `TimeSavings` |
| `Comp` | Compensatory time entries | `Comp` |
To consider **all** entry types when computing the current total, pass all values explicitly. The default only covers the three most common accrual types.
### Form-data fields
| Field | Required | Description |
| :-------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
| `File` | Yes | The CSV or XLSX import file. |
| `Description` | Yes | Label visible to users in the account details. |
| `ReferenceDate` | Yes | Reference date in `yyyy-MM-ddTHH:mm:ss` format. |
| `EntryTypes` | No | Comma-separated list of entry types to consider when computing the current total. Default: `AutoAccruals,ManualAccruals,Regularizations`. |
### Example request
```http theme={null}
POST /timmi-absences/api/public/services/v1.0/leaveEntitlementsReplace?strict=false&simulate=false HTTP/1.1
Host: {yourDomain}
Authorization: lucca application={apiKey}
Content-Type: multipart/form-data; boundary=----Boundary
------Boundary
Content-Disposition: form-data; name="File"; filename="entitlements.csv"
Content-Type: application/octet-stream
LegalEntityCode;EmployeeNumber;LastName;FirstName;CP N;RTT
001;10045;Doe;Jane;25;10
------Boundary
Content-Disposition: form-data; name="Description"
Payroll sync - April 2026
------Boundary
Content-Disposition: form-data; name="ReferenceDate"
2026-04-01T00:00:00
------Boundary
Content-Disposition: form-data; name="EntryTypes"
AutoAccruals,ManualAccruals,Regularizations
------Boundary--
```
# Migrate from leaves v2
Source: https://developers.luccasoftware.com/api-reference/legacy/timmi-absences/guides/migrate-from-leaves-v2
## Introduction
The v2 API will be shut down on March 31, 2026, and will no longer respond after this date.
This guide helps you migrate from the deprecated v2 leaves API endpoint to the v3.
**Why is the v2 deprecated?**
The v2 leaves API endpoint:
* suffers from significant performance issues, that also affect other API endpoints;
* relies on older technology, that may suffer from security vulnerabilities.
Whereas the v3 leaves API endpoint:
* offers faster response times;
* gives you additional features (pagination, sorting);
* shares the same authentication system than v2.
**Are you affected by this change?**
If there's any HTTP request against the `/api/leaves` API endpoint in your integration, then you are affected by this and
must migrate to `/api/v3/leaves` before 2026-03-31. Otherwise, you are free to skip this.
## Changes
The V3 Leaves API endpoint is documented in the [API Reference](https://developers.lucca.fr/api-reference/legacy/timmi-absences/leaves/list-leaves).
### Routes
| V2 | V3 |
| :------------ | :--------------- |
| `/api/leaves` | `/api/v3/leaves` |
### Query parameters
| api v2 | api v3 | Description |
| :--------- | :-------------------- | :----------------------------------------------- |
| `owner.id` | `leavePeriod.ownerId` | Filter leaves by the employee ID they belong to. |
Note: other query parameters did not change.
### Response
In V2, all responses are a JSON object with a `data` property which is:
* a JSON object for a response containing a singular resource;
* a JSON array for a response containing a collection of resources.
In V3, all responses are a JSON object with a `data` property which is always a JSON object. For collections,
it contains an `items` property, which is a JSON aray which contains the representations of resources in the collection.
```yaml V2 Response theme={null}
type: object
properties:
data:
oneOf:
- title: Single Leave
$ref: "#/leave"
- title: Collection of Leaves
type: array
items:
$ref: "#/leave"
```
```yaml V3 Response theme={null}
type: object
properties:
data:
oneOf:
- title: Single Leave
$ref: "#/leave"
- title: Collection of Leaves
type: object
properties:
items:
type: array
items:
$ref: "#/leave"
```
### Fields
Some of the fields were renamed, moved, removed and/or changed type.
| V2 | V3 Equivalent | Description |
| :--------------------------------------------- | -------------------------------- | :---------------------------------------------------------------------------------------------------------------------- |
| `id: string` | `id: string` | **ID generation pattern changed**-see below. |
| `status: string` | `status: string` | Represents the approval status of the leave. **The enum was changed**-see below. |
| `owner.id: integer` | `leavePeriod.ownerId: integer` | ID of the employee this leave belongs to. The owner is now a property of the `leavePeriod`. |
| `owner.name: string` | `leavePeriod.owner.name: string` | Name of the employee this leave belongs to. |
| `owner.mail: string` | N/A | No longer available for security reasons\*. |
| `owner.login: string` | N/A | No longer available for security reasons\*. |
| `owner.matricule: string` | N/A | No longer available for security reasons\*. |
| `name: string` | `leaveAccount.name: string` | In v2, the `name` was equal to the name of the leave account. |
| `duration: integer` | `duration: string