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

# Migrate from leaves v2

## Introduction

<Danger>The v2 API will be shut down on March 31, 2026, and will no longer respond after this date.</Danger>

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

<Tip>The V3 Leaves API endpoint is documented in the [API Reference](https://developers.lucca.fr/api-reference/legacy/timmi-absences/leaves/list-leaves).</Tip>

### 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.

<CodeGroup>
  ```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"
  ```
</CodeGroup>

### 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<enum>`                         | `status: string<enum>`           | 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<email>`                    | 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<time>`         | Duration of the leave, in minutes in v2 (e.g. `720`), as a time in v3 (e.g. `"12:00:00"`).                              |
| `accountCode: string`                          | `leaveAccount.id: integer`       | Unique identifier of the leave account.                                                                                 |
| `accountLabel: string`                         | `leaveAccount.name: string`      | Name of the leave account (e.g. "Vacations", "Sickness" …)                                                              |
| N/A                                            | `isActive`                       | Whether the leave was deleted. A deleted leave used to completely disappear in v2, but it can still be retrieved in v3. |
| <code>leaveScope: string = 'AM' \| 'PM'</code> | `isAM: boolean`                  | Whether the leave belong to the morning or the afternoon.                                                               |

<Tip>**\*** : Fields related to the leave owner are no longer available in v3 in order to prevent data leaks. You may retrieve them
with a complementary request to `/api/v3/users`.</Tip>

Note: other fields did not change.

#### About Leave ID

```abnf V2 ABNF Syntax for Leave ID theme={null}
leave-id = owner-id "-" date-time "-" meridiem

; Owner identifier (adjust as needed)
owner-id = 1*(ALPHA / DIGIT / "_" / "-")

; Date and time: dd/MM/yyyy hh:mm:ss
date-time = day "/" month "/" year SP hour ":" minute ":" second

day    = 2DIGIT  ; 01–31 (range not enforced in ABNF)
month  = 2DIGIT  ; 01–12
year   = 4DIGIT

hour   = 2DIGIT  ; 01–12
minute = 2DIGIT  ; 00–59
second = 2DIGIT  ; 00–59

meridiem = "AM" / "PM"

SP = %x20

; Example: 416-05/09/2025 11:42:30-AM
```

```abnf V3 ABNF Syntax for Leave ID theme={null}
leave-id = leave-period-id "-" date "-" meridiem

; Leave period identifier (adjust as needed)
leave-period-id = 1*(ALPHA / DIGIT / "_" / "-")

; Date in YYYYMMDD format
date  = year month day

year  = 4DIGIT
month = 2DIGIT  ; 01–12 (range not enforced)
day   = 2DIGIT  ; 01–31 (range not enforced)

meridiem = "AM" / "PM"

; Example: 416-20250905-AM
```

#### About Leave Status

```typescript V2 Leave Status Enum theme={null}
enum EV2Status {
  // Leave request was submitted but still has not been touched by a manager.
  NonValidated = 0,
  // The request has been partially approved by management.
  SemiValidated = 1, 
  // The request was approved by all required managers.
  Validated = 2, 
  // The request was rejected by a manager.
  Rejected = 3, 
  // The request was cancelled by the requesting employee themselves.
  Cancelled = 4, 
}
```

```typescript V3 Leave Status Enum theme={null}
enum EV3Status {
  // The leave request has been submitted but is not approved
  // (can still be partially approved).
  Temporary = 0,
  // The leave request is formally approved.
  Confirmed = 1,
}
```

## Code Examples

### JSON Examples

<CodeGroup>
  ```json V2 theme={null}
  {
    "header": {
      "generated": "2025-12-09T14:20:38.7951403+01:00",
      "serverTime": 17716,
      "queryTime": 17642,
      "processId": 1336
    },
    "data": [
      {
        "id": "3464-11/07/2025 00:00:00-AM",
        "name": "Télétravail",
        "owner": {
          "id": 3464,
          "name": "Daniel HERNANDEZ"
        },
        "date": "2025-07-11T00:00:00",
        "duration": 720,
        "status": 1
      },
      {
        "id": "3464-11/07/2025 12:00:00-PM",
        "name": "RTT 2025",
        "owner": {
          "id": 3464,
          "name": "Daniel HERNANDEZ"
        },
        "date": "2025-07-11T12:00:00",
        "duration": 720,
        "status": 1
      }
    ]
  }
  ```

  ```json V3 theme={null}
  {
    "header": {
      "generated": "2025-12-09T14:19:57.0907857",
      "principal": "Lucca Admin",
      "processId": 2492
    },
    "data": {
      "items": [
        {
          "id": "217979-20250711-AM",
          "leavePeriod": {
            "owner": {
              "name": "Daniel HERNANDEZ",
              "id": 3464
            }
          },
          "name": "217979-20250711-AM",
          "date": "2025-07-11T00:00:00",
          "status": 1,
          "duration": "12:00:00"
        },
        {
          "id": "215197-20250711-PM",
          "leavePeriod": {
            "owner": {
              "name": "Daniel HERNANDEZ",
              "id": 3464
            }
          },
          "name": "215197-20250711-PM",
          "date": "2025-07-11T00:00:00",
          "status": 1,
          "duration": "12:00:00"
        }
      ]
    },
    "metadata": null
  ```
</CodeGroup>

### Example Mapper

<CodeGroup>
  ```typescript V3 To V2 (typescript) theme={null}
  enum EV2Status {
    NonValidated = 0,
    SemiValidated = 1,
    Validated = 2,
    Rejected = 3,
    Cancelled = 4,
  }

  enum EV3Status {
    Temporary = 0,
    Confirmed = 1,
  }

  interface IV2Leave {
      id: string;
      date: string;
      owner: {
        id: number;
        name: string;
        mail: string | null;
        login: string | null;
        matricule: string | null;
      };
      name: string;
      status: EV2Status;
      duration: number;
      accountCode: string;
      accountLabel: string;
      leaveScope: 'am' | 'pm';
  }

  interface IV3Leave {
      id: string;
      date: string;
      leavePeriod: {
          owner: {
              id: number;
              name: string;
          },
      };
      status: EV3Status;
      isActive: boolean;
      leaveAccount: {
          id: number;
          name: string;
      };
      duration: string;
      isAm: boolean;
  }

  function minutesSinceMidnight(timeStr: string) {
    const [hours, minutes, seconds = 0] = timeStr.split(':').map(Number);

    return hours * 60 + minutes + seconds / 60;
  }


  function v3ToV2Leave(v3leave: IV3Leave): IV2Leave {
    function v3ToV2Status(v3leave: IV3Leave): EV2Status {
      if (!v3leave.isActive) return EV2Status.Rejected
      if (v3leave.status === EV3Status.Temporary) {
        return EV2Status.NonValidated;
      } else {
        return EV2Status.Validated;
      }
    }
    function v3ToV2Id(v3leave: IV3Leave): string {
      const match = v3leave.id.match(/^(.+)-(\d{4})(\d{2})(\d{2})-(AM|PM)$/);

      if (!match) {
        throw new Error("Invalid leave ID format");
      }

      const [, leavePeriodId, year, month, day, meridiem] = match;

      const time = meridiem === "AM" ? "00:00:00" : "12:00:00";

      return `${v3leave.leavePeriod.ownerId}-${day}/${month}/${year} ${time}-${meridiem}`;
    }

    return {
      id: v3ToV2Id(v3leave),
      date: v3leave.date,
      owner: {
        id: v3leave.leavePeriod.owner.id,
        name: v3leave.leavePeriod.owner.name,
        mail: null, // No longer available for security reasons, you may fetch the user with GET /api/v3/users/{id}?fields=mail.
        login: null, // No longer available for security reasons, you may fetch the user with GET /api/v3/users/{id}?fields=login.
        matricule: null, // No longer available for security reasons, you may fetch the user with GET /api/v3/users/{id}?fields=employeeNumber.
      },
      name: v3leave.leaveAccount.name,
      status: v3ToV2Status(v3leave),
      duration: minutesSinceMidnight(v3leave.duration),
      accountCode: v3leave.leaveAccount.id.toString(),
      accountLabel: v3leave.leaveAccount.name,
      leaveScope: v3leave.isAm ? 'am' : 'pm',
    }
  }
  ```
</CodeGroup>
