Ключевой принцип: все сущности связываются через внешние идентификаторы (
extId) — GUID или коды из вашей
системы. Вам не нужно хранить внутренние ID Exode: департаменты, должности и трудоустройства адресуются по
extId, пользователи находятся по extId через user/find и
user/upsert.1С → Exode: готовый пример синхронизации
Полный разобранный пример под 1С:ЗУП: BSL-код цикла выгрузки (update по extId → NotFound → create),
JSON-маппинг полей 1-в-1, домены из логинов, должности по GUID и предусловия.
Схема связей
Как связаны сущности модуля staff между собой:- Департамент — подразделение школы; иерархия строится через
parentId(parentExtId). - Трудоустройство — центральная связка: пользователь + департамент + должность, плюс условия
занятости (
kind,type,rate). - Руководитель департамента привязывается не к пользователю напрямую, а к его активному трудоустройству.
- Отсутствие (отпуск, больничный, командировка) тоже привязано к трудоустройству.
Порядок синхронизации
1
Департаменты
Создайте оргструктуру методами раздела Департаменты. Передавайте
extId (код подразделения из вашей системы) и parentExtId для иерархии.Родительский департамент должен существовать на момент создания дочернего — синхронизируйте дерево
сверху вниз (или отсортируйте выгрузку так, чтобы родители шли раньше детей).2
Должности
Создайте должности методами раздела Должности —
name и extId.
Правильный ключ — GUID должности из вашей системы (extId), а не имя: имя изменяемо и не уникально на
вашей стороне (переименовали должность — и резолв по имени сломается). Актуализируйте имя при каждой
синхронизации через PUT /staff/position/ext/{extId}/update, а при отсутствии — создавайте
(StaffPositionNotFound → POST /staff/position/create).Если у должностей вообще нет кодов (в выгрузке только job_position_name) — работает fallback: найдите
должность по имени через GET /staff/position/list?search=..., при отсутствии создайте.3
Сотрудники
Создать нового сотрудника можно методом
create или upsert (существующих обновляют через update). Что
выбрать, зависит от вашей выгрузки — в первую очередь от того, нужно ли задавать пароль: подробно в разделе
Каким методом заводить сотрудника. Трудоустройства во всех случаях
передаются массивом в extra.staff.employments (каждый элемент — positionExtId + departmentExtId,
опционально extId, type, kind, startAt, rate). Для корпоративной школы при создании пользователя
массив обязателен и должен содержать хотя бы одно трудоустройство (иначе StaffEmploymentInputRequired).Если сотрудник числится в нескольких подразделениях — передайте несколько элементов в
extra.staff.employments (от 1 до 10): первое трудоустройство с kind: "Main", остальные —
kind: "InternalSecondary" (или ExternalSecondary для внешнего совместительства).4
Руководители
Назначьте руководителей департаментов методом
staff/department-manager/set: departmentExtId +
employmentExtId (внешний идентификатор трудоустройства руководителя, который вы передали при найме).
Основной руководитель помечается isPrimary: true.Если в вашей системе руководитель указывается у каждого сотрудника (например, leader_external_ids), а не у
подразделения — назначайте руководителем департамента и/или сохраните ссылку на руководителя в кастомном поле
сотрудника.5
Остальные данные — кастомные поля
Для данных, которым нет поля в схеме пользователя (отчество, город, теги, произвольный статус из CRM,
дополнительный email), создайте кастомные поля в макете формы и записывайте значения
через
custom-field/value/set-by-slug после апсерта пользователя.6
Увольнения
При увольнении вызовите
staff/employment/terminate
(по extId трудоустройства). Закрытие последнего активного трудоустройства автоматически переводит
пользователя в статус Terminated и закрывает ему доступ — отдельно блокировать его не нужно. Если у
сотрудника остаются другие активные трудоустройства, увольнение из одного из них — это структурное изменение,
доступ сохраняется. Чтобы заблокировать доступ без увольнения, передайте status: "Blocked" в
user/update — все активные сессии будут завершены; см.
Жизненный цикл статуса.Если ваша система отдаёт полный список сотрудников (а не изменения) — получите текущие активные
трудоустройства через staff/employment/list, сравните с выгрузкой по
extId и увольте отсутствующих.Каким методом заводить сотрудника
Создать нового сотрудника умеют толькоcreate и upsert. update пользователя не создаёт — он лишь
обновляет существующего (и участвует в паттерне «update → NotFound → create»). Выбор между create и upsert
зависит от того, нужно ли вам задавать пароль.
POST /user/create— создаёт нового. Если пользователь с таким логином (email/phone/domain),tgIdилиextIdуже существует — вернётся ошибка (EmailIsBusy,PhoneIsBusy,DomainIsBusy,TgIdIsBusy,ExtIdIsBusy). Пароль (password) применяется.PUT /user/upsert— «создать или обновить» за один вызов. Ищет существующего по логину /tgId/extId: найден → обновляет, нет → создаёт. Идемпотентен — повторная выгрузка того же сотрудника не создаёт дубль и не падает.PUT /user/ext/{extId}/update(илиPUT /user/{userId}/update) — только обновляет существующего. Если пользователя нет —NotFound(создания не будет). Пароль этим методом не задаётся.
- Пароль вы не задаёте (генерируете и рассылаете на нашей стороне или он не нужен) → берите
upsert: один вызов, идемпотентно, проще всего для регулярной синхронизации. - Пароль задаёте вы (например, детерминированный «имя+фамилия+год») → заводите новых через
create(там пароль точно применится), а существующих обновляйте черезupdate. Типовой паттерн:updateпоextId→ приNotFound→create(всё адресуется поextId, внутренние ID Exode не нужны). Черезupsertтак не выйдет: если пользователь уже есть,upsertуйдёт в обновление и заданный вами пароль не применится.
Поведение назначений employments
Массивemployments в user/create, user/update и user/upsert применяется идемпотентно и умеет ровно
одно — «донанять, если такого активного назначения ещё нет». Это ключевое отличие от «полной синхронизации»,
и его легко упустить:
- Повтор той же пары департамент + должность — безопасный no-op. Слать одно и то же назначение при каждом синке можно — дубль не создастся.
- Новая пара — это новое назначение (совместительство), а не перевод. Если сотрудника в 1С перевели и вы
пришлёте в
employmentsновую пару, у него станет два активных назначения (старое и новое), а не одно. - Отсутствие назначения в массиве ≠ увольнение. Не пришли назначение — с ним ничего не произойдёт, оно
останется активным. Убрать его можно только явным
terminate.
extra, а отдельными эндпоинтами
раздела Трудоустройства:
Практический вывод для инкрементальной выгрузки: в
user/ext/{extId}/update для существующего сотрудника
extra.staff обычно не передают — иначе перевод в 1С превратится в лишнее совместительство. Донайм через
extra уместен, когда вы осознанно добавляете сотруднику ещё одно место работы.Жизненный цикл статуса пользователя
Полеuser.status (Active / OnLeave / Banned / Blocked / Terminated / Deleted) частично управляется
автоматически. Доступ закрывают Banned, Blocked, Terminated и Deleted; Active и OnLeave — оставляют
вход открытым.
- Увольнение с последнего активного трудоустройства (
employment/terminate) автоматически переводит пользователя вTerminated: вход запрещён, все сессии завершаются. Повторный наём (hire) сотрудника со статусомTerminatedавтоматически возвращаетActive. Нельзя уволить с последнего назначения самого себя (StaffCannotTerminateSelf) или владельца школы (StaffCannotTerminateSchoolOwner). - Блокировка админом —
user/update { "status": "Blocked" }: вход закрыт, сессии завершаются, новые зачисления на продукты запрещены; данные сохраняются и видны в отчётах.{ "status": "Active" }снимает блокировку. Владельца школы заблокировать нельзя. OnLeaveуправляется модулем отсутствий: при действующем отсутствии (отпуск/больничный) статус автоматически становитсяOnLeave, по завершении — возвращается вActive.OnLeaveинформационный — вход остаётся доступен, вручную его ставить не нужно.- Бан и разбан —
user/update { "status": "Banned" }закрывает доступ. При снятии бана (передайтеActive) фактический статус пересчитывается автоматически по трудоустройствам и отсутствиям: уволенный вернётся вTerminated, находящийся в отпуске — вOnLeave, остальные — вActive.
Синхронизация
OnLeave работает и по календарю: помимо пересчёта при API-вызовах, платформа раз в час
автоматически переводит сотрудников в OnLeave при наступлении даты начала отсутствия и возвращает в Active
по его окончании. Заводить собственный периодический пересчёт больше не нужно.Маппинг статуса из выгрузки: «Работает» →
Active (снимет ранее выставленную блокировку). «Уволен»
не передаётся статусом — увольнение оформляется через employment/terminate, Terminated выставится сам.Маппинг типовых полей
Тело запросаuser/create или user/upsert с комментариями — откуда какое поле берётся и что важно знать
(// ← показывает исходное поле вашей выгрузки):
- Отчество, город, теги, произвольный статус из CRM, доп. email → кастомные поля
(
custom-field/value/set-by-slugпосле создания/апсерта пользователя). - Руководители (
leader_external_ids) → отдельный вызовstaff/department-manager/set(departmentExtId+employmentExtId). - Роль/теги без аналога — игнорируются.
Ограничения, о которых стоит знать заранее
Минимальный пример: сотрудник с трудоустройством
cURL
cURL
Обновлено: 2026-07-14 22:58 UTC