Тема
Семейство: records
Семейство устаревает 2026-12-31. Преемники — в разделе «Переход с универсальной записи на протоколы».
norm_record_targets
Куда вообще можно писать записи: ярлыки целей, названия для человека и вид карточки. Ярлык отсюда передаётся аргументом target в остальные глаголы записи. Спрашивайте, когда цель неизвестна или их может быть несколько. Доменное состояние not_ready — ни одной цели не настроено: это ждёт человека, повторный вызов ничего не изменит.
Доступ: чтение · Списковый: нет · Версии: 1 · Закат: 2026-12-31
Вход
| Поле | Обязательно | Тип | Значение |
|---|
Данные ответа
| Поле | При успехе | Тип | Значение |
|---|---|---|---|
error | нет | DomainError | Доменное состояние |
targets | да | список RecordTarget |
Пример: успешный ответ
json
{
"source_refs": [
{
"source": "company-data",
"ref": "record:targets",
"label": "Цели записи"
}
],
"freshness": {
"as_of": "2026-09-21T03:00:00.000Z",
"ttl_sec": 86400,
"stale": false
},
"data": {
"targets": [
{
"id": "meeting-protocols",
"name": "Протоколы встреч",
"kind": "meeting_protocol"
}
]
}
}Пример: случай «domain_error»
json
{
"source_refs": [
{
"source": "company-data",
"ref": "record:targets"
}
],
"freshness": {
"as_of": "2026-09-21T09:30:00.000Z",
"ttl_sec": 0,
"stale": false
},
"data": {
"error": {
"code": "not_ready",
"message": "Ни одна цель записи не настроена: не задано, куда писать карточки. Это ждёт человека.",
"retryable": false
}
}
}norm_record_schema
Из чего состоит карточка целевой записи: поля, их типы, что обязательно и какие значения допустимы. Спрашивайте ПЕРЕД тем как заполнять запись — состав полей у каждой компании свой. У поля бывает роль — объявленный смысл из словаря вида цели: такое поле заполняется прямо из данных предмета. Поле без роли предметное, его значение выбирается из вариантов по опорным признакам. Идентификаторы людей для полей типа person берите в norm_people_list. Свойства, не отобразившиеся в шесть типов, перечислены в skipped с причиной — это не сбой. Доменные состояния: not_ready ждёт человека, schema_unavailable — источник не синхронизирован, unknown_target — ярлык неизвестен, со списком допустимых.
Доступ: чтение · Списковый: нет · Версии: 1 · Закат: 2026-12-31
Вход
| Поле | Обязательно | Тип | Значение |
|---|---|---|---|
target | нет | string |
Данные ответа
| Поле | При успехе | Тип | Значение |
|---|---|---|---|
error | нет | DomainError | Доменное состояние |
fields | да | список RecordField | |
kind | да | RecordKind | Вид цели записи |
skipped | да | список SkippedField | |
target | да | string | |
warnings | нет | список string |
Пример: успешный ответ
json
{
"source_refs": [
{
"source": "company-data",
"ref": "record:schema:meeting-protocols",
"label": "Описание карточки"
}
],
"freshness": {
"as_of": "2026-09-21T03:00:00.000Z",
"ttl_sec": 86400,
"stale": false
},
"data": {
"target": "meeting-protocols",
"kind": "meeting_protocol",
"fields": [
{
"key": "f-title",
"name": "Название",
"type": "text",
"required": true,
"role": "title"
},
{
"key": "f-date",
"name": "Дата встречи",
"type": "date",
"required": true,
"role": "meeting_date"
},
{
"key": "f-link",
"name": "Ссылка на запись",
"type": "url",
"required": false,
"role": "source_url"
},
{
"key": "f-people",
"name": "Участники",
"type": "person",
"required": false,
"role": "participants"
},
{
"key": "f-owner",
"name": "Организатор",
"type": "person",
"required": false,
"role": "organizer"
},
{
"key": "f-agenda",
"name": "Повестка",
"type": "text",
"required": false,
"role": "agenda"
},
{
"key": "f-status",
"name": "Статус",
"type": "choice_one",
"required": true,
"role": "status",
"closed": true,
"options": [
{
"id": "o-draft",
"name": "Черновик",
"role": "draft",
"has_hint": false
},
{
"id": "o-done",
"name": "Готово",
"role": "done",
"has_hint": false
},
{
"id": "o-none",
"name": "Встречи не было",
"role": "no_meeting",
"has_hint": false
},
{
"id": "o-cancel",
"name": "Отменено",
"has_hint": false
}
]
},
{
"key": "f-type",
"name": "Тип встречи",
"type": "choice_one",
"required": false,
"closed": true,
"fallback_hint_option_id": "o-weekly",
"options": [
{
"id": "o-weekly",
"name": "Планёрка",
"aliases": [
"планёрка",
"еженедельная"
],
"org_unit_id": "u-sales",
"has_hint": true,
"description": "Регулярная встреча подразделения по текущим задачам"
},
{
"id": "o-top",
"name": "Руководители",
"membership_kind": "pool",
"members": [
"a.novikova@example.com",
"s.ilin@example.com"
],
"has_hint": false
}
]
}
],
"skipped": [
{
"name": "Длительность",
"type": "формула",
"reason": "вычисляемое свойство в контракт не отображается"
}
],
"warnings": []
}
}Пример: случай «domain_error»
json
{
"source_refs": [
{
"source": "company-data",
"ref": "record:schema"
}
],
"freshness": {
"as_of": "2026-09-21T09:30:00.000Z",
"ttl_sec": 0,
"stale": false
},
"data": {
"error": {
"code": "unknown_target",
"message": "Ярлык цели «протоколы» неизвестен. Запись в цель по умолчанию не выполняется.",
"retryable": false,
"allowed_targets": [
"meeting-protocols",
"meeting-protocols-staging"
]
}
}
}norm_record_find
Есть ли уже записи по этим ключам — спрашивайте пакетом до 200 ключей за вызов, а не по одной. Так узнают, что уже обработано: своего списка обработанного у потребителя нет. Ответ идёт в том же порядке, что и запрос. exists: false — записи нет, можно создавать. exists: true вместе с deleted: true — запись была и её удалил человек: создавать заново нельзя. Пустой индекс — не ошибка, просто все ключи exists: false.
Доступ: чтение · Списковый: нет · Версии: 1 · Закат: 2026-12-31 · Замена: norm_protocols_list
Вход
| Поле | Обязательно | Тип | Значение |
|---|---|---|---|
keys | да | список string | |
target | нет | string |
Данные ответа
| Поле | При успехе | Тип | Значение |
|---|---|---|---|
error | нет | DomainError | Доменное состояние |
records | да | список FoundRecord | |
target | да | string |
Пример: успешный ответ
json
{
"source_refs": [
{
"source": "company-data",
"ref": "record:index:meeting-protocols",
"label": "Индекс записей"
}
],
"freshness": {
"as_of": "2026-09-21T09:30:00.000Z",
"ttl_sec": 86400,
"stale": false
},
"data": {
"target": "meeting-protocols",
"records": [
{
"key": "meeting_protocol:01K9F2QH7T8ZRC4M0X5B2VNJ3D",
"exists": false
},
{
"key": "meeting_protocol:01K9F0YB3M6QA8T2WD7E4RSKPN",
"exists": true,
"url": "https://records.example.com/01K9F0YB3M6QA8T2WD7E4RSKPN"
},
{
"key": "meeting_protocol:01K9EZZC2P4NM6S1VB8H3TQXWR",
"exists": true,
"deleted": true
}
]
}
}Пример: случай «domain_error»
json
{
"source_refs": [
{
"source": "company-data",
"ref": "record:index"
}
],
"freshness": {
"as_of": "2026-09-21T09:30:00.000Z",
"ttl_sec": 0,
"stale": false
},
"data": {
"error": {
"code": "unknown_target",
"message": "Настроено несколько целей записи — укажите ярлык в аргументе target.",
"retryable": false,
"allowed_targets": [
"meeting-protocols",
"meeting-protocols-staging"
]
}
}
}norm_record_hint
Подсказка под ОДИН выбранный вариант поля: текст, который компания просит учесть, когда значение поля именно такое. Спрашивайте только для варианта, который уже выбрали, и один раз за прогон на повторяющийся вариант. В описании записи этих текстов нет намеренно: они не влезают в один ответ, поэтому там остаётся только признак has_hint. Поля hint в ответе нет — у варианта подсказки нет, это нормально: собирайте на своём каркасе. truncated: true — текст обрезан, НЕ применяйте его: отрезан конец, где обычно и лежат требования.
Доступ: чтение · Списковый: нет · Версии: 1 · Закат: 2026-12-31 · Замена: norm_protocol_types
Вход
| Поле | Обязательно | Тип | Значение |
|---|---|---|---|
field | да | string | |
option | да | string | |
target | нет | string |
Данные ответа
| Поле | При успехе | Тип | Значение |
|---|---|---|---|
error | нет | DomainError | Доменное состояние |
field | да | string | |
hint | нет | RecordHint | Подсказка под вариант |
option | да | string | |
target | да | string | |
truncated | да | boolean |
Пример: успешный ответ
json
{
"source_refs": [
{
"source": "company-data",
"ref": "record:hint:f-type:o-weekly",
"label": "Подсказка"
}
],
"freshness": {
"as_of": "2026-09-21T03:00:00.000Z",
"ttl_sec": 86400,
"stale": false
},
"data": {
"target": "meeting-protocols",
"field": "f-type",
"option": "o-weekly",
"truncated": false,
"hint": {
"text": "Протокол планёрки: сначала решения списком, затем поручения с исполнителем и сроком, в конце вопросы без ответа.",
"mode": "replace"
}
}
}Пример: случай «ok_no_hint»
json
{
"source_refs": [
{
"source": "company-data",
"ref": "record:hint:f-type:o-top"
}
],
"freshness": {
"as_of": "2026-09-21T03:00:00.000Z",
"ttl_sec": 86400,
"stale": false
},
"data": {
"target": "meeting-protocols",
"field": "f-type",
"option": "o-top",
"truncated": false
}
}Пример: случай «domain_error»
json
{
"source_refs": [
{
"source": "company-data",
"ref": "record:hints"
}
],
"freshness": {
"as_of": "2026-09-21T09:30:00.000Z",
"ttl_sec": 0,
"stale": false
},
"data": {
"error": {
"code": "schema_unavailable",
"message": "Цель настроена, но её описание ещё не прочитано: источник не синхронизирован.",
"retryable": true
}
}
}norm_record_upsert
Создать или переписать запись в системе компании: поля карточки плюс тело в markdown. Идемпотентно по key: повторный вызов с тем же ключом переписывает ТУ ЖЕ запись. Поля берите из norm_record_schema. Тело заменяется целиком, поля объединяются: непереданное поле остаётся как было, а поле, где уже есть значение человека, не затирается (оно вернётся в kept_existing). Запись создаётся ДАЖЕ если обязательное поле пустое — отражайте незавершённость статусом, а не отказом от записи. Непринятое поле не отменяет операцию: оно приедет в rejected_fields успешного ответа, там же unresolved_people — люди без учётной записи в системе компании. Доменные состояния: record_deleted — запись удалил человек, воссоздавать нельзя; record_rejected — хранилище отклонило запись по форме, повтор даст тот же ответ; temporary и rate_limited — повторите позже; not_ready и schema_unavailable ждут человека или синхронизации.
Доступ: запись · Списковый: нет · Версии: 1 · Закат: 2026-12-31 · Замена: norm_protocol_upsert
Вход
| Поле | Обязательно | Тип | Значение |
|---|---|---|---|
body | да | string | |
fields | нет | список FieldValue | |
key | да | string | |
target | нет | string | |
title | нет | string |
Данные ответа
| Поле | При успехе | Тип | Значение |
|---|---|---|---|
created | да | boolean | |
error | нет | DomainError | Доменное состояние |
kept_existing | да | список string | |
key | да | string | |
note | нет | string | |
rejected_fields | да | список RejectedField | |
target | да | string | |
unresolved_people | да | список string | |
url | нет | string (uri) |
Пример: успешный ответ
json
{
"source_refs": [
{
"source": "company-data",
"ref": "record:meeting-protocols",
"label": "Копия записи для поиска"
},
{
"source": "record",
"ref": "https://records.example.com/01K9F2QH7T8ZRC4M0X5B2VNJ3D",
"label": "Карточка записи"
}
],
"freshness": {
"as_of": "2026-09-21T09:31:00.000Z",
"ttl_sec": 0,
"stale": false
},
"data": {
"key": "meeting_protocol:01K9F2QH7T8ZRC4M0X5B2VNJ3D",
"target": "meeting-protocols",
"url": "https://records.example.com/01K9F2QH7T8ZRC4M0X5B2VNJ3D",
"created": true,
"rejected_fields": [
{
"key": "f-duration",
"reason": "вычисляемое свойство: значение задаёт хранилище"
}
],
"unresolved_people": [
"s.ilin@example.com"
],
"kept_existing": [
"f-status"
]
}
}Пример: случай «domain_error»
json
{
"source_refs": [
{
"source": "company-data",
"ref": "record:meeting-protocols"
}
],
"freshness": {
"as_of": "2026-09-21T09:31:00.000Z",
"ttl_sec": 0,
"stale": false
},
"data": {
"key": "meeting_protocol:01K9EZZC2P4NM6S1VB8H3TQXWR",
"error": {
"code": "record_deleted",
"message": "Эту запись удалил человек. Создавать её заново нельзя — это его решение.",
"retryable": false
}
}
}