Users (v4)
The Users API lets you create and manage employee accounts in Ditio — the typical backbone of an HR-system sync. Each user needs at minimum a name, phone number, date of birth, employee number, and employment start date.
Base URL: api/v4/integration/users · Scope: ditioapiv3
# Test (default for all examples)export DITIO_API_BASE="https://core-api.ditio.dev/core"# Production: export DITIO_API_BASE="https://core-api.ditio.app/core"Create a user
Section titled “Create a user”POST /api/v4/integration/userscurl -X POST "$DITIO_API_BASE/api/v4/integration/users" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "companyId": "YOUR_COMPANY_ID", "employeeNumber": "1042", "firstName": "Kari", "lastName": "Nordmann", "mobileWork": "+4798765432", "birthDate": "1990-05-15", "employmentStartDate": "2026-03-01", "email": "[email protected]", "department": "Anlegg", "workTitle": "Maskinfører", "immediateSupervisors": [ { "supervisorId": "240", "primary": true } ], "tags": [ { "name": "safety-course", "enabled": true } ] }'immediateSupervisors attaches the employee’s foreman or site manager, and
tags attaches qualifications — both in the same call that creates the user.
Neither is required; see Supervisors and
User tags for the details.
Response: the created user, including identityId and
companyProfileId.
{ "identityId": "auth0|abc123def456", "companyProfileId": "67a1b2c3d4e5f6a7b8c9d0e3", "companyId": "YOUR_COMPANY_ID", "employeeNumber": "1042", "firstName": "Kari", "lastName": "Nordmann", "mobileWork": "+4798765432", "immediateSupervisors": null, "immediateSupervisorDetails": [ { "userId": "3f2a9c14-8b7d-4e52-9a01-6c4d2e8f1b73", "employeeNumber": "240", "name": "Ola Nordmann", "companyId": "YOUR_COMPANY_ID", "primary": true } ], "tags": null}Note that the supervisor you just set comes back under
immediateSupervisorDetails, while immediateSupervisors and tags read as
null. That’s expected — both are write-only fields. See
Reading supervisors back.
Required fields
Section titled “Required fields”| Field | Type | Format | Description |
|---|---|---|---|
companyId | string | Your Ditio company ID | |
employeeNumber | string | Unique employee number within the company | |
firstName | string | First name | |
lastName | string | Last name | |
mobileWork | string | +4798765432 | Work phone number (used for SMS login) |
birthDate | string | dd.MM.yyyy or yyyy-MM-dd | Date of birth |
employmentStartDate | string | dd.MM.yyyy or yyyy-MM-dd | Employment start date |
Optional fields
Section titled “Optional fields”| Field | Type | Description |
|---|---|---|
email | string | Email address |
mobilePrivate | string | Private phone number |
workTitle | string | Job title |
department | string | Department name |
employmentEndDate | string | Employment end date (to terminate employment forward in time) |
mainProjectNumber | string | Default project number for this employee |
carRegNumber | string | Company car registration number |
address | string | Home address |
closestRelative | string | Emergency contact information |
personalInfo | string | Additional personal information |
cardId | string | Builder card / access card ID |
cardExpirationDate | string | Card expiration date |
payroll | bool | true = hourly wage, false = fixed salary |
isDisabled | bool | Disable the user account (default: false) |
defaultResourceNumber | string | Default resource/machine number |
worktimeArrangementName | string | Work time arrangement name |
organizationNumber | string | Organization number (for sub-contractor companies) |
tags | array | User tags to attach or detach (see below) |
immediateSupervisors | array | Who the user reports to (see below) |
User tags
Section titled “User tags”Each entry names a tag and says whether the user should have it. A tag that doesn’t exist in the company yet is created the first time you use it.
{ "tags": [ { "name": "safety-course", "enabled": true }, { "name": "crane-license", "enabled": false } ]}| Field | Type | Description |
|---|---|---|
name | string | Tag name. Matched case- and whitespace-insensitively |
enabled | bool | true attaches the tag to the user, false detaches it |
Only the tags you list are touched — anything you leave out keeps its current
state. Omitting tags, or sending an empty array, changes nothing.
Supervisors
Section titled “Supervisors”immediateSupervisors sets who a user reports to. This is the field to use when
you create an employee and want their foreman or site manager attached at the
same time.
{ "immediateSupervisors": [ { "supervisorId": "240", "primary": true } ]}| Field | Type | Description |
|---|---|---|
supervisorId | string | Identifies the supervisor — see the accepted forms below |
primary | bool | Marks the main supervisor. At most one entry may be true |
What supervisorId accepts
Section titled “What supervisorId accepts”Any one of these — they’re tried in order, and the first match wins:
| Form | Example | Notes |
|---|---|---|
| Employee number | 240 | Recommended. Validated, and the number you already sync on |
| Phone number | +4798765432 or 98765432 | Matched against the user’s login number. Local format is normalised using the company’s country code |
Profile ID (companyProfileId) | 001234-698db70bed0b5f4ab12e4367 | Exact match, restricted to your own company tree |
Ditio user ID (identityId) | 3f2a9c14-8b7d-4e52-9a01-6c4d2e8f1b73 | Taken as-is, not verified — see the warning below |
| Full name | Ola Nordmann | "first last", substring match — see the caution below |
Both of a user’s identifiers work, so you can pass whichever one you already hold:
| Field | Looks like | Notes |
|---|---|---|
identityId | 3f2a9c14-8b7d-4e52-9a01-6c4d2e8f1b73 | Accepted, but not checked against a real user |
companyProfileId | 001234-698db70bed0b5f4ab12e4367 | Accepted, and validated against your company tree |
Replace, keep, or clear
Section titled “Replace, keep, or clear”The list replaces whatever the user has now:
| You send | Result |
|---|---|
field omitted, or null | supervisors left untouched |
[] | all supervisors removed |
| one or more entries | those become the complete set |
Two entries resolving to the same person collapse into one. More than one
primary: true returns 400 Only one supervisor can be marked as primary.
Reading supervisors back
Section titled “Reading supervisors back”You write immediateSupervisors but read immediateSupervisorDetails.
GET always returns "immediateSupervisors": null — that’s expected, and not a
sign the write failed. The stored supervisors come back resolved:
{ "immediateSupervisors": null, "immediateSupervisorDetails": [ { "userId": "3f2a9c14-8b7d-4e52-9a01-6c4d2e8f1b73", "employeeNumber": "240", "name": "Ola Nordmann", "companyId": "YOUR_COMPANY_ID", "primary": true } ]}Check immediateSupervisorDetails to confirm a write landed. An empty array
means nothing was stored.
Create a user with a supervisor
Section titled “Create a user with a supervisor”curl -X POST "$DITIO_API_BASE/api/v4/integration/users" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "companyId": "YOUR_COMPANY_ID", "employeeNumber": "1042", "firstName": "Kari", "lastName": "Nordmann", "mobileWork": "+4798765432", "birthDate": "1990-05-15", "employmentStartDate": "2026-03-01", "immediateSupervisors": [ { "supervisorId": "240", "primary": true } ] }'Get users
Section titled “Get users”All users in your company
Section titled “All users in your company”GET /api/v4/integration/usersOptional query parameters:
| Parameter | Type | Description |
|---|---|---|
changedSince | datetime | Only return users modified after this date (note: slower query) |
companyId | string | Filter to a specific company (useful if you have subsidiaries) |
employmentLookbackDays | int | Only return users with active or recently ended employments (within X days) |
# All users changed in the last 7 dayscurl "$DITIO_API_BASE/api/v4/integration/users?changedSince=2026-06-27T00:00:00Z" \ -H "Authorization: Bearer $TOKEN"
# Only active employees (employment still active or ended within 30 days)curl "$DITIO_API_BASE/api/v4/integration/users?employmentLookbackDays=30" \ -H "Authorization: Bearer $TOKEN"By company profile ID
Section titled “By company profile ID”GET /api/v4/integration/users/by-profile-id/{profileId}By employee number
Section titled “By employee number”GET /api/v4/integration/users/by-employee-number/{employeeNumber}Update a user (full)
Section titled “Update a user (full)”PUT /api/v4/integration/users/{identityId}Despite the name, PUT does not replace the user. It merges the fields you send onto the stored user, exactly like PATCH — a field you leave out keeps its current value rather than being blanked.
What actually separates PUT from PATCH is validation, not merge behaviour: PUT
rejects the request with 400 unless the body carries identityId,
companyProfileId, companyId, employeeNumber, firstName, lastName,
mobileWork, birthDate, and employmentStartDate.
Sending null for a field does clear it on PUT. If you mean “no change”,
leave the field out instead.
curl -X PUT "$DITIO_API_BASE/api/v4/integration/users/auth0%7Cabc123def456" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "identityId": "auth0|abc123def456", "companyProfileId": "67a1b2c3d4e5f6a7b8c9d0e3", "companyId": "YOUR_COMPANY_ID", "employeeNumber": "1042", "firstName": "Kari", "lastName": "Nordmann", "mobileWork": "+4798765432", "birthDate": "1990-05-15", "employmentStartDate": "2026-03-01", "workTitle": "Anleggsleder" }'Update a user (partial)
Section titled “Update a user (partial)”PATCH /api/v4/integration/users/{companyProfileId}Updates only the fields you include, and requires no mandatory fields. This is
the endpoint to use for routine updates. Note that PATCH uses
companyProfileId in the URL, not identityId.
curl -X PATCH "$DITIO_API_BASE/api/v4/integration/users/67a1b2c3d4e5f6a7b8c9d0e3" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "workTitle": "Prosjektleder", "department": "Administrasjon", "immediateSupervisors": [{ "supervisorId": "240", "primary": true }] }'Unlike PUT, an explicit null is ignored by PATCH rather than clearing the
field. To clear a value use PUT — or, for supervisors, send an empty array.
PUT or PATCH?
Section titled “PUT or PATCH?”Both merge, so choose on ergonomics rather than semantics:
PUT /{identityId} | PATCH /{companyProfileId} | |
|---|---|---|
| URL key | identityId (URL-encode it) | companyProfileId |
| Required fields | 9 mandatory fields, else 400 | none |
| Fields you omit | unchanged | unchanged |
Explicit null | clears the field | ignored |
| Best for | full record sync from an HR master | targeted updates |
Disable / enable a user
Section titled “Disable / enable a user”Instead of deleting a user, disable and re-enable them:
# Disablecurl -X PATCH "$DITIO_API_BASE/api/v4/integration/users/disable/67a1b2c3d4e5f6a7b8c9d0e3" \ -H "Authorization: Bearer $TOKEN"
# Enablecurl -X PATCH "$DITIO_API_BASE/api/v4/integration/users/enable/67a1b2c3d4e5f6a7b8c9d0e3" \ -H "Authorization: Bearer $TOKEN"Both return 204 No Content on success.
Delete a user
Section titled “Delete a user”DELETE /api/v4/integration/users/{identityId}curl "$DITIO_API_BASE/api/v4/integration/users/is-identity-deletable/auth0%7Cabc123def456" \ -H "Authorization: Bearer $TOKEN"The response is an object, not a bare boolean. When the user can’t be deleted,
validationErrors explains why:
{ "isDeletable": true, "validationErrors": []}DELETE returns 204 No Content on success.
Common integration patterns
Section titled “Common integration patterns”Sync employees from an HR system
Section titled “Sync employees from an HR system”- Initial load — create all employees with
POST /api/v4/integration/users - Ongoing sync — periodically fetch
GET /api/v4/integration/users?changedSince=LAST_SYNC_TIMEand compare with your HR system - Updates — use
PATCHto update changed fields - Offboarding —
PATCH /disable/{companyProfileId}when an employee leaves (or setemploymentEndDate)
Troubleshooting supervisors
Section titled “Troubleshooting supervisors”| Symptom | Cause |
|---|---|
GET shows "immediateSupervisors": null after a successful write | Expected — read immediateSupervisorDetails instead |
immediateSupervisorDetails is [] after a 200 | Nothing was stored. On older builds PATCH dropped the field — retry with PUT |
Supervisor listed with "name": null | The supervisorId was a GUID that matches no user. Use the employee number |
400 Could not resolve SupervisorId '001234-…' | A companyProfileId on a deployment older than profile-ID support, or a profile belonging to another company. Send the employee number |
400 Could not resolve SupervisorId '…' | The name, employee number, or phone number matched no user in the company |
| Wrong person attached | A name matched more than one user. Names are substring-matched and ties are broken arbitrarily — use the employee number |
400 Only one supervisor can be marked as primary | More than one entry had primary: true |
| Supervisor disappeared after an update | The request sent immediateSupervisors: [], which clears the list. Omit the field to leave it alone |
Handling employee numbers
Section titled “Handling employee numbers”employeeNumber is the primary identifier for matching users between
systems. It must be unique within a company. If your HR system doesn’t have
employee numbers, generate sequential ones (e.g. 001, 002, …).
Related
Section titled “Related”- Employees API v5 — the modern replacement, recommended for new integrations
- SCIM Provisioning — directory-driven provisioning from Entra ID
- Certificates — attach qualifications to users by employee number
- Help centre: Brukerhåndtering — user administration in Ditio Web (Norwegian)