Skip to main content
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.

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:

employment

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.

job-position

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.

probationary-period

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.

employee-attributes (unified read endpoint)

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 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 100 items per page; for extension endpoints the limit is 1,000.
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:
ResourceRequired scope
employmentemployments.readonly
job-positionjob-positions.readonly
probationary-periodsprobationary-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 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.

Authentication

Read more about how to obtain and use OAuth 2.0 bearer tokens.

Versioning

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 fieldV5 resourceV5 fieldV5 concept
establishmentIdjob-positionbusinessEstablishmentWhere the employee physically works.
(no v4 equivalent)employmentlegalEntityLegal 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:
// 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 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:
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 fieldV5 resourceV5 fieldNotes
idemploymentidNew ID; v4 and v5 IDs are not the same value — do not assume identity
ownerIdemployment / job-positionemployee.idBoth resources reference the employee
externalIdemploymentremoteIdRenamed; no uniqueness constraint in v5
typeIdemploymenttemplate.idNow a reference to the employment-template resource
startsOnemploymentstart.dateNow a nested object: { "date": "YYYY-MM-DD" }
endsOnemploymentend.dateSame wrapping object; null for open-ended contracts
isApplicableemploymentstatusisApplicable: truestatus: "active". Read-only in both versions
establishmentIdjob-positionbusinessEstablishment.idMoved from the contract to the job-position
spcIdjob-positionoccupationCategory.idRenamed; still references the same occupation categories
trialPeriodEndDateprobationary-periodinitialEndsOnMoved to the separate probationary-period resource
trialPeriodEndDate2probationary-periodextendedEndsOnSame 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
hiringTypeIdemployee-attributee_generated_hiringTypeThe hiring type is now stored as an employee attribute.
temporaryContractGroundIdemployee-attributee_generated_temporaryContractGroundThe temporary contract ground is now stored as an employee attribute.
internshipSupervisorIdemployee-attributee_generated_internshipSupervisorIdThe internship supervisor is now stored as an employee attribute.
terminationReasonIdemployee-attributee_generated_terminationReasonIdThe termination reason is now stored as an employee attribute.
createdAtemployment or job-position or employee-attributecreatedAtSame semantics
lastModifiedAtemployment or job-position or employee-attributelastUpdatedAtRenamed
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 100 items (default 25).

Pagination

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.
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
If you only need the currently active employment, add &status=active to the query parameters.

Retrieve a single employment by ID

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

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

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 endpoint while making sure to set applicability.asOf={TODAY}.

Further Reading

Migrating from /api/v3/users

Migration guide for the v3 user object, covering personal data, employment, and job-position fields.

Authentication

How to obtain and use OAuth 2.0 bearer tokens.

Employment resource

Full reference for the v5 employment resource.

Job-position resource

Full reference for the v5 job-position resource.

Get Started With Employee Attributes

How to use the unified employee-attributes endpoint to read any employee property in a single call.