User job assignments

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:

CodeCause
FUTURE_USER_JOB_ASSIGNMENTS_HAS_NO_PRIMARYThe result would leave a window with assignments but no primary
MULTIPLE_PRIMARY_FOR_ONE_DATE_EFFECTIVEThe 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:

IntentCall
The user stops holding the job on a known datePATCH with dateUnassign
The end date was wrong and there should be nonePATCH with dateUnassign: null
The assignment should never have existedDELETE
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).