Тема
Люди и оргструктура
Семейство people.
norm_org_units
Дерево подразделений компании целиком. Состав подразделения — сигнал, по которому подразделение встречи определяется без модели: если большинство участников числится в одном подразделении, выбор сделан. Членство прямое, принадлежность верхним уровням выводится по полю parent_id. Доменное состояние not_ready — оргструктура не подключена: это ждёт человека, повторный вызов ничего не изменит.
Доступ: чтение · Списковый: нет · Версии контракта: 1
Аргументы
Аргументов нет.
Данные ответа (data)
| Поле | При успехе | Тип | Что это |
|---|---|---|---|
units | да | список OrgUnit | |
error | нет | DomainError | Доменное состояние |
Пример: успешный ответ
json
{
"source_refs": [
{
"source": "company-data",
"ref": "org:units",
"label": "Подразделения"
}
],
"freshness": {
"as_of": "2026-09-21T03:00:00.000Z",
"ttl_sec": 86400,
"stale": false
},
"data": {
"units": [
{
"id": "u-root",
"name": "Компания",
"parent_id": null,
"kind": "company",
"sort_order": 0
},
{
"id": "u-sales",
"name": "Продажи",
"parent_id": "u-root",
"kind": "division",
"code": "012",
"sort_order": 10
},
{
"id": "u-retail",
"name": "Розница",
"parent_id": "u-sales",
"kind": "department",
"sort_order": 20
}
]
}
}Пример: случай «domain_error»
json
{
"source_refs": [
{
"source": "company-data",
"ref": "org:units"
}
],
"freshness": {
"as_of": "2026-09-21T03:00:00.000Z",
"ttl_sec": 0,
"stale": false
},
"data": {
"error": {
"code": "not_ready",
"message": "Оргструктура не подключена: нет источника подразделений и людей. Это ждёт человека — повторный вызов ничего не изменит.",
"retryable": false
}
}
}norm_people_list
Сотрудники компании страницами. Поле id — то значение, которое передаётся в поля протокола с людьми (participant_ids, organizer_id). Почты приходят в нижнем регистре, и у одного человека их бывает несколько: сопоставляйте по любой из них. Для следующей страницы передайте page.next_cursor как cursor; нет курсора — люди дочитаны. total — сколько людей всего с учётом фильтров. role_id отбирает тех, у кого есть назначение с этой ролью: так отвечают на вопрос «кто занимает роль». unit_id отбирает по прямому членству, без спуска в дочерние подразделения: спускайтесь сами по дереву norm_org_units. active_only не передан — отдаются все, кого хранит сервер; true — скрыты те, у кого дата увольнения уже наступила. Доменное состояние not_ready — оргструктура не подключена.
Доступ: чтение · Списковый: да · Версии контракта: 1
Аргументы
| Поле | Обязательно | Тип |
|---|---|---|
active_only | нет | boolean |
cursor | нет | string |
limit | нет | integer |
role_id | нет | string |
unit_id | нет | string |
Данные ответа (data)
| Поле | При успехе | Тип | Что это |
|---|---|---|---|
people | да | список Person | |
total | да | integer | |
error | нет | DomainError | Доменное состояние |
Пример: успешный ответ
json
{
"source_refs": [
{
"source": "company-data",
"ref": "org:people",
"label": "Сотрудники"
}
],
"freshness": {
"as_of": "2026-09-21T03:00:00.000Z",
"ttl_sec": 86400,
"stale": false
},
"page": {
"has_more": false
},
"data": {
"total": 2,
"people": [
{
"id": "p-1042",
"name": "Анна Новикова",
"aliases": [
"Новикова А.",
"a.novikova"
],
"emails": [
"a.novikova@example.com",
"79001234567@example.com"
],
"unit_ids": [
"u-sales",
"u-retail"
],
"phone": "+7 900 123-45-67",
"handles": [
"@a_novikova",
"anna.novikova"
],
"manager_id": "p-1077",
"hired_at": "2024-02-01",
"assignments": [
{
"id": "a-1042-sales",
"unit_id": "u-sales",
"role_id": "r-cco",
"role_name": "Коммерческий директор",
"is_primary": true,
"is_head": false,
"started_at": "2024-02-01"
},
{
"id": "a-1042-retail",
"unit_id": "u-retail",
"role_id": "r-head-retail",
"role_name": "Руководитель розницы",
"is_primary": false,
"is_head": true
}
],
"extra": {
"grade": "M2",
"location": "Москва"
}
},
{
"id": "p-1077",
"name": "Михаил Петров",
"aliases": [
"Петров М."
],
"emails": [
"m.petrov@example.com"
],
"unit_ids": [
"u-retail"
]
}
]
}
}Пример: случай «domain_error»
json
{
"source_refs": [
{
"source": "company-data",
"ref": "org:people"
}
],
"freshness": {
"as_of": "2026-09-21T03:00:00.000Z",
"ttl_sec": 0,
"stale": false
},
"page": {
"has_more": false
},
"data": {
"error": {
"code": "not_ready",
"message": "Оргструктура не подключена: нет источника подразделений и людей. Это ждёт человека — повторный вызов ничего не изменит.",
"retryable": false
}
}
}norm_roles_list
Роли компании страницами: то, чем занимаются её люди. У компании с обычным кадровым учётом это справочник должностей, и у роли есть только идентификатор и название; компания, которая описывает роли смыслом, добавляет продукт роли, заказчика и подчинение. Идентификатор роли возвращается в назначениях сотрудника и принимается фильтром norm_people_list. unit_id отбирает роли, привязанные к подразделению. Пустой список — законный ответ компании, которая ролей не ведёт; not_ready означает, что оргструктура не подключена вовсе.
Доступ: чтение · Списковый: да · Версии контракта: 1
Аргументы
| Поле | Обязательно | Тип |
|---|---|---|
cursor | нет | string |
limit | нет | integer |
unit_id | нет | string |
Данные ответа (data)
| Поле | При успехе | Тип | Что это |
|---|---|---|---|
roles | да | список Role | |
total | да | integer | |
error | нет | DomainError | Доменное состояние |
Пример: успешный ответ
json
{
"source_refs": [
{
"source": "company-data",
"ref": "org:roles",
"label": "Роли"
}
],
"freshness": {
"as_of": "2026-09-21T03:00:00.000Z",
"ttl_sec": 86400,
"stale": false
},
"page": {
"has_more": false
},
"data": {
"total": 2,
"roles": [
{
"id": "r-head-retail",
"name": "Руководитель розницы",
"aliases": [
"Директор розничной сети"
],
"product": "Выполненный план продаж розничной сети при сохранённой марже",
"customer": "Коммерческий директор",
"reports_to_role_id": "r-cco",
"unit_id": "u-retail",
"description": "Отвечает за выручку и маржу всех торговых точек."
},
{
"id": "r-cco",
"name": "Коммерческий директор"
}
]
}
}Пример: случай «domain_error»
json
{
"source_refs": [
{
"source": "company-data",
"ref": "org:roles"
}
],
"freshness": {
"as_of": "2026-09-21T03:00:00.000Z",
"ttl_sec": 0,
"stale": false
},
"page": {
"has_more": false
},
"data": {
"error": {
"code": "not_ready",
"message": "Оргструктура не подключена: справочник ролей брать неоткуда. Это ждёт человека — повторный вызов ничего не изменит.",
"retryable": false
}
}
}norm_person_get
Карточка одного сотрудника целиком. Передайте id или email — хотя бы один обязателен, без обоих вызов отклоняется как invalid_input. Передали оба — карточку определяет id, почта игнорируется. Почта сравнивается без учёта регистра. Уволенный отдаётся всегда: вы спросили конкретного человека. Доменные состояния: unknown_user — ни идентификатор, ни почта не подошли; record_rejected — на эту почту в компании заведено больше одной карточки, и выбрать за человека нельзя; not_ready — оргструктура не подключена.
Доступ: чтение · Списковый: нет · Версии контракта: 1
Аргументы
| Поле | Обязательно | Тип |
|---|---|---|
email | нет | string |
id | нет | string |
Данные ответа (data)
| Поле | При успехе | Тип | Что это |
|---|---|---|---|
person | да | Person | Сотрудник |
error | нет | DomainError | Доменное состояние |
Пример: успешный ответ
json
{
"source_refs": [
{
"source": "company-data",
"ref": "org:people/p-1042",
"label": "Карточка сотрудника"
}
],
"freshness": {
"as_of": "2026-09-21T03:00:00.000Z",
"ttl_sec": 86400,
"stale": false
},
"data": {
"person": {
"id": "p-1042",
"name": "Анна Новикова",
"aliases": [
"Новикова А.",
"a.novikova"
],
"emails": [
"a.novikova@example.com",
"79001234567@example.com"
],
"unit_ids": [
"u-sales",
"u-retail"
],
"phone": "+7 900 123-45-67",
"handles": [
"@a_novikova",
"anna.novikova"
],
"manager_id": "p-1077",
"hired_at": "2024-02-01",
"assignments": [
{
"id": "a-1042-sales",
"unit_id": "u-sales",
"role_id": "r-cco",
"role_name": "Коммерческий директор",
"is_primary": true,
"is_head": false,
"started_at": "2024-02-01"
},
{
"id": "a-1042-retail",
"unit_id": "u-retail",
"role_id": "r-head-retail",
"role_name": "Руководитель розницы",
"is_primary": false,
"is_head": true
}
],
"extra": {
"grade": "M2",
"location": "Москва"
}
}
}
}Пример: случай «domain_error»
json
{
"source_refs": [
{
"source": "company-data",
"ref": "org:people"
}
],
"freshness": {
"as_of": "2026-09-21T03:00:00.000Z",
"ttl_sec": 0,
"stale": false
},
"data": {
"error": {
"code": "unknown_user",
"message": "Ни идентификатор, ни почта не подошли ни к одному сотруднику. Проверьте значение: идентификаторы приходят из norm_people_list.",
"retryable": false
}
}
}norm_person_upsert
Создать или обновить карточку сотрудника. Ключ — id или email, правило то же, что у norm_person_get. По ключу id карточка только обновляется: нет такой — unknown_user. По ключу email карточка обновляется или создаётся; для создания обязательно имя, иначе ответ несёт record_rejected и поле name среди непринятых. Что делают переданные поля: поля нет во входе — оно не трогается; переданный массив заменяет прежний набор целиком; значение null у одиночного поля очищает его; внутри extra то же правило по каждому ключу. Непринятое поле — не отказ: остальное записано, а поле названо в rejected_fields с причиной. Доменные состояния: not_ready — запись профилей у компании не настроена; unknown_user — карточки по ключу id нет; record_rejected — ничего не записано (на почту заведено больше одной карточки, при создании нет имени, хранилище отвергло карточку целиком); unknown_field — компания не ведёт поле ядра, которое вы пытаетесь записать. Реализация вправе разрешить запись только привилегированным ролям и отвечать unauthorized_scope.
Доступ: запись · Списковый: нет · Версии контракта: 1
Аргументы
| Поле | Обязательно | Тип |
|---|---|---|
aliases | нет | список string |
assignments | нет | список Assignment |
email | нет | string |
emails | нет | список string |
extra | нет | object |
handles | нет | список string |
hired_at | нет | string,null (date) |
id | нет | string |
manager_id | нет | string | null |
name | нет | string |
phone | нет | string | null |
terminated_at | нет | string,null (date) |
Данные ответа (data)
| Поле | При успехе | Тип | Что это |
|---|---|---|---|
created | да | boolean | |
id | да | string | |
rejected_fields | да | список RejectedField | |
error | нет | DomainError | Доменное состояние |
url | нет | string (uri) |
Пример: успешный ответ
json
{
"source_refs": [
{
"source": "company-data",
"ref": "org:people/p-1042",
"label": "Карточка сотрудника"
}
],
"freshness": {
"as_of": "2026-09-21T09:31:00.000Z",
"ttl_sec": 0,
"stale": false
},
"data": {
"id": "p-1042",
"created": false,
"url": "https://people.example.com/p-1042",
"rejected_fields": [
{
"key": "extra.birthday",
"reason": "поле не ведётся в карточке сотрудника"
}
]
}
}Пример: случай «created»
json
{
"source_refs": [
{
"source": "company-data",
"ref": "org:people/p-1103",
"label": "Карточка сотрудника"
}
],
"freshness": {
"as_of": "2026-09-21T09:31:00.000Z",
"ttl_sec": 0,
"stale": false
},
"data": {
"id": "p-1103",
"created": true,
"url": "https://people.example.com/p-1103",
"rejected_fields": []
}
}Пример: случай «domain_error»
json
{
"source_refs": [
{
"source": "company-data",
"ref": "org:people"
}
],
"freshness": {
"as_of": "2026-09-21T09:31:00.000Z",
"ttl_sec": 0,
"stale": false
},
"data": {
"error": {
"code": "unknown_user",
"message": "Карточки с таким идентификатором нет. По ключу id карточка только обновляется; чтобы завести новую, передайте email и имя.",
"retryable": false
}
}
}Объекты семейства
Объекты, из которых состоят аргументы и ответы инструментов этого семейства.
OrgUnit
Подразделение. Идентификатор, название и родитель. У корневого подразделения родитель — null. Принадлежность верхним уровням выводится по цепочке родителей. Необязательные kind, code и sort_order — вид узла, код в учётной системе компании и порядок среди соседей.
| Поле | Обязательно | Тип | Что это |
|---|---|---|---|
id | да | string | |
name | да | string | |
parent_id | да | string | null | |
code | нет | string | |
kind | нет | OrgUnitKind | Вид подразделения |
sort_order | нет | integer |
OrgUnitKind
Вид подразделения. Уровень узла в дереве: юридическое лицо, дивизион, отдел, команда, группа. Вид описателен: вложенность задаётся только полем parent_id.
Значения: company, division, department, team, group.
Person
Сотрудник. Идентификатор для полей протокола с людьми (participant_ids, organizer_id), имя, другие написания имени, все написания почты в нижнем регистре и прямое членство в подразделениях. handles — опознавательные строки вне почты (логины в мессенджерах): сопоставляйте их так же, как почту. Когда отданы назначения, unit_ids равно множеству их подразделений. Руководителя ищите так: явный manager_id; если его нет — человек с признаком is_head в основном подразделении; если и его нет — по цепочке reports_to_role_id роли основного назначения. extra — поля, которые ведёт сама компания: их состав контракт не описывает.
| Поле | Обязательно | Тип |
|---|---|---|
aliases | да | список string |
emails | да | список string |
id | да | string |
name | да | string |
unit_ids | да | список string |
assignments | нет | список Assignment |
extra | нет | object |
handles | нет | список string |
hired_at | нет | string (date) |
manager_id | нет | string |
phone | нет | string |
terminated_at | нет | string (date) |
Assignment
Назначение. Где человек числится и в какой роли. is_primary — основное место работы; is_head — человек руководит этим подразделением. Роль приходит идентификатором, а у компании без справочника должностей — только названием в role_name; назначение вовсе без роли означает «числится в подразделении».
| Поле | Обязательно | Тип |
|---|---|---|
is_head | да | boolean |
is_primary | да | boolean |
unit_id | да | string |
ended_at | нет | string (date) |
id | нет | string |
role_id | нет | string |
role_name | нет | string |
started_at | нет | string (date) |
Role
Роль. Чем человек занимается в компании. В обычном кадровом учёте роль — это должность, и у неё есть только идентификатор и название. Компания, которая описывает роли смыслом, добавляет product (что роль производит), customer (главный заказчик результата) и reports_to_role_id (кому роль подчиняется). Цикл в подчинении контракт не запрещает и не проверяет: обходя цепочку, защищайтесь от него сами.
| Поле | Обязательно | Тип |
|---|---|---|
id | да | string |
name | да | string |
aliases | нет | список string |
customer | нет | string |
description | нет | string |
product | нет | string |
reports_to_role_id | нет | string |
unit_id | нет | string |
RejectedField
Непринятое поле. Поле, которое запись не приняла, и причина. Операция при этом выполнена.
| Поле | Обязательно | Тип |
|---|---|---|
key | да | string |
reason | да | string |