Quick Summary: An assignment links a person to a career at a level. Assigning grants that level’s learnings and attaches the career’s skills; unassigning records career history first, so a person’s past careers remain readable.
The assigned person
Returned byGET /api/v1/careers/{id}/people.
The two dates have different owners.
assignedAt is stamped by SkillsDB at the moment of the write and cannot be supplied; initiationDate is yours, and records when the person actually begins in the career.The person’s career
Returned byGET /api/v1/people/{personId}/careers — the {personId} slot takes the person’s email address (or the opaque id from any response). Previous (ended) assignments are included by default — pass ?includePrevious=false to exclude them.
The distribution
Returned byGET /api/v1/careers/{id}/distribution. Counted server-side — do not page through the assigned-people list to derive these numbers.
Each bucket is
{ id, label, count }, where id is null for the catch-all bucket (for example No Flag).
A credential bound to a manager sees only their span of control, so these counts reflect what the acting person is permitted to see — not necessarily the whole company.
Two rules that surprise integrations
GET /api/v1/careers/{id}/people lists the career’s holders only — every row carries a level and an assignedAt; an empty career is an empty list. GET /careers/{id}/distribution aggregates the same population server-side.
Assigning is never an update. POST /api/v1/careers/{id}/people takes a list of people — identified by email address — each at their own level:
PATCH /api/v1/careers/{id}/people/{personId} with { "levelId": "13" } — the {personId} slot takes the person’s email (or the opaque id from any response); an integration that re-assigns expecting a correction will do nothing at all.
Assigning fans out. Beyond the assignment row, it grants every learning attached to the target level and attaches the career’s skills to the person. One call against a hundred people is a large write, not a small one — budget it against your rate limit.
Unassigning mirrors the shape by email: DELETE /api/v1/careers/{id}/people with { "emails": ["ada@example.com", "grace@example.com"] }. Both assign and unassign accept at most 500 people per call — a larger array is rejected with a 400 validation-error before anything is written, so split bigger batches into chunks.