User job assignments
TIP
View the Job Management API Reference
A user's job history is a timeline under /v1/users/{userId}/job-assignments. A user may hold several jobs at once, and among the assignments in effect exactly one is the primary.
Four endpoints cover the whole surface, but the rules around the primary flag and Position Management matter more than the calls themselves.
Check Position Management first
While Position Management is active for the portal, every write on this timeline is refused with 400 USER_JOB_ASSIGNMENT_MANAGED_BY_POSITION_MANAGEMENT. Reads keep working.
This is a portal-wide setting, not a per-user one, so confirm it before building an integration that writes job assignments. There is no partial mode.
Assigning a job
POST /services/api/x/chr-jobmgmt-app/v1/users/{userId}/job-assignments
Content-Type: application/json
{
"jobId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"dateEffective": "2026-01-01",
"isPrimary": true,
"notes": "Promotion"
}
Only jobId and dateEffective are required. Omit dateUnassign for an open-ended assignment, which reads back with endDate: null. A future dateEffective schedules the assignment, which comes back isFuture: true and does not yet count towards the jobs the user holds.
Because a user may hold several jobs, this does not disturb their other assignments, with one exception: isPrimary: true moves the primary flag onto this assignment and demotes whichever other assignment in effect held it.
The response returns the new assignmentId and its URI, also in the Location header.
The primary job invariant
This is the rule that causes most rejections.
Every window of time that has any assignment in effect must have exactly one primary. The API enforces it on write, across the whole timeline including future entries, not just today.
Two failures follow from it:
| Code | Cause |
|---|---|
FUTURE_USER_JOB_ASSIGNMENTS_HAS_NO_PRIMARY | The result would leave a window with assignments but no primary |
MULTIPLE_PRIMARY_FOR_ONE_DATE_EFFECTIVE | The result would give a window two primaries |
So if a user holds one job and you assign a second without isPrimary, the first stays primary and the call succeeds. But ending the primary assignment while another remains in effect can leave a window with no primary, and that is rejected. Promote the survivor in the same change rather than ending the primary and fixing it afterwards.
isPrimary is null on ended assignments
isPrimary reports null for entries that have already ended, since the flag only has meaning while an assignment is in effect. Do not treat null as false.
Ending an assignment
Three different operations, with different meanings:
| Intent | Call |
|---|---|
| The user stops holding the job on a known date | PATCH with dateUnassign |
| The end date was wrong and there should be none | PATCH with dateUnassign: null |
| The assignment should never have existed | DELETE |
PATCH .../job-assignments/{assignmentId}
Content-Type: application/merge-patch+json
{ "dateUnassign": "2026-06-30" }
dateUnassign is inclusive, so the user holds the job through that whole day.
DELETE takes the entry off the timeline entirely, which is not the same as ending it. Use it only to remove a mistake, since it erases the fact that the user ever held the job.
The job cannot be changed
An assignment's jobId is fixed. To move a user to a different job, end the current assignment and create a new one. Patching jobId is not supported.
Moving a user to a new job
The common case, expressed as two calls:
PATCH .../job-assignments/{oldAssignmentId}
{ "dateUnassign": "2026-06-30" }
POST .../job-assignments
{ "jobId": "<new job>", "dateEffective": "2026-07-01", "isPrimary": true }
Ending on the 30th and starting on the 1st leaves no gap, because dateUnassign is inclusive. If the old assignment was the primary, set isPrimary on the new one so no window is left without a primary.
Reading assignments
From the user's side, the full timeline:
GET /services/api/x/chr-jobmgmt-app/v1/users/{userId}/job-assignments
Each entry carries jobId, the three date fields, the isCurrent / isHistoric / isFuture flags and isPrimary. The job's title, code and structure are not repeated, so read GET v1/jobs/{jobId} for those.
From the job's side, who holds it:
GET /services/api/x/chr-jobmgmt-app/v1/jobs/{jobId}/assigned-users
Note the asymmetry. The user side is a timeline you can write to; the job side is a read-only view of current holders.
Other limits
A user holds at most 1000 assignments across all jobs (USER_JOB_TIMELINE_RECORD_COUNT_EXCEEDS_MAX_LIMIT_GUARD_RAIL). Assigning the same job twice with the same dateEffective is DUPLICATE_DATE_EFFECTIVE, and the shared date rules apply as everywhere else.
Permissions
Writes on this timeline need two security permissions, and they answer different questions:
Job Management API - Assignment - Manage (User)decides which users you may assign.Job Management API - Assignment - Manage (Job)decides which jobs you may assign them to.
Both must cover the request, so a caller constrained to one division can only assign that division's users to that division's jobs. Reads need Job Management API - Assignment - View (User).