PUT /saas/v2/user/ext/{extId}/update (updates the profile
by external ID); if the employee does not exist (NotFound), POST /saas/v2/user/create
(creates the user and hires them via the extra.staff.employments array). The user,
department and position are addressed directly by extId, so you do not need our system’s internal
IDs at all. extra is a container for related entities: staff.employments for now, and later
custom field values and so on.
What actually goes to the API for Ivanov: a 1-to-1 mapping
The input item from the export:{ "success": true, "code": 201, "payload": { "user": { ... } } }: the user is created and hired
into “ПОДР-001” as “Manager” in a single call.
An existing employee: update the profile by extId, no internal ID needed
(if the user does not exist, you get NotFound, and then you go to create):
extra.staff here, on purpose: sending the employment again is harmless (an idempotent no-op),
but if the employee was transferred to another department in 1C, extra would create a second
employment for them (secondary employment) rather than a transfer. HR changes go through /staff/employment/*.
extra is also supported in update, in the same format as in create. Let’s look at what happens
to each employments element:
extra.employments can only “make sure the employment exists”
(if not, hire; if it does, skip). It CANNOT close or change existing employments:
- the employee was transferred →
POST /staff/employment/ext/{extId}/transfer; - promoted (position changed) →
POST /staff/employment/ext/{extId}/promote; - dismissed →
POST /staff/employment/ext/{extId}/terminate.
employments,
NOTHING happens to it, and it stays active. The array is not a “full list to
sync”: an employment missing from it ≠ termination of that employment. You can remove an employment
only with an explicit terminate call. So you can send one employment or all of them:
listed ones are hired or skipped, unlisted ones are left untouched.
If you send a new pair in extra during a transfer, you get an employee with TWO active
employments (the old and the new one), not a transfer.
Secondary employment: several employments in one array (up to 10):
_):
What to do when something changes in 1C
The loop above is idempotent: you can run it as often as every hour, and repeated runs are safe. It picks up profile changes on its own, but HR changes it does not; they have separate calls. All of them are addressed by the employmentextId (the same extId you passed in the
employments element when hiring, which is why it matters to set it).
Examples of HR calls (the employment extId from the example above is
i.ivanov.01011990:ПОДР-001). For
readability, the extId in paths is shown as is; in a real request, encode it
(КодироватьСтроку(..., СпособКодированияСтроки.КодировкаURL)), otherwise Cyrillic characters in the path will not arrive correctly:
i.ivanov.01011990%3A%D0%9F%D0%9E%D0%94%D0%A0-001.
i.ivanov.01011990:ПОДР-001 still
points to the employee’s current employment. If the extId embeds a department code (as in the
example), it will no longer match the actual department; this is not an error, it is just a key.
If you want precision, use the ID of the employment record from 1C as the extId.
Prerequisites
- Departments (
ПОДР-001, etc.) must be created in advance:POST /saas/v2/staff/department/create { "name": "Sales Department", "extId": "ПОДР-001", "parentExtId": "ПОДР-000" }. IfdepartmentExtIdis not found, create returnsStaffDepartmentNotFoundand the user is not created. - HR changes for an existing employee go through the employment endpoints by employment extId:
transfer:
POST /staff/employment/ext/{extId}/transfer { "toDepartmentExtId": "ПОДР-002" }, position change:.../promote { "toPositionExtId": "<GUID>" }, termination:.../terminate. A new employment (including secondary employment):POST /staff/employment/hire, orextra.staff.employmentsinuser/update/user/upsert, which hires idempotently (repeating the same active department + position pair is a no-op); transfers and terminations cannot be expressed throughextra. - Termination of the last active employment automatically closes the account:
user.statusbecomesTerminated, all sessions are ended, and sign-in is blocked. A repeated hire (hire) automatically restoresActive. You cannot terminate the school owner or yourself (StaffCannotTerminateSchoolOwner/StaffCannotTerminateSelf). - The
Blockedstatus (blocked by an admin) is set viauser/update { "status": "Blocked" }: sign-in is disabled, sessions are ended, and new course assignments are prohibited; the data is kept and visible in reports.{ "status": "Active" }lifts the block, and access is restored. The school owner cannot be blocked. - Absences control the
OnLeavestatus: when an employee’s absence becomes effective (vacation, sick leave, etc.),user.statusautomatically becomesOnLeave; when it ends or is deleted, it returns toActive.OnLeaveis an informational status: sign-in remains available. leader_external_idsmaps not to the employee but to the department manager, with a separate call:POST /saas/v2/staff/department-manager/set { "departmentExtId": "ПОДР-001", "employmentExtId": "<employment extId>", "isPrimary": true }. PassisPrimaryexplicitly on every call:setwithout it turns the primary manager into a regular one.statusfrom the export maps touser.status: “Работает” (Working) →"Active". “Уволен” (Dismissed) is not passed directly as a status: termination is done viaemployment/terminate, andTerminatedis set automatically.OnLeaveis not set manually either; absences control it.role/tag_list/city/email_for_notificationshave no counterpart in the API; these fields are ignored.- Employees without email: pass
domain(Latin letters/digits/_/dots; a dot not at the edges and not consecutive; the server converts it to lowercase; unique within the school; if taken →DomainIsBusy) and alwayspassword: without email and phone, domain + password becomes the only way to sign in. - Positions by GUID (
positionExtId), not by name: the name is mutable and not unique on the 1C side (“someone adds a soft sign and the integration breaks”). The GUID is optional: without it, the name fallback works. Limitation: a position name is unique within the school, so you cannot create two positions with the same name but different GUIDs (different organizations in 1C) (StaffPositionNameIsNotUniq); consolidate the GUIDs into one or do not pass a GUID for the second organization.
Updated: 2026-09-25 14:33 UTC