SIRINVIDEO CCTV Документация администратора и пользователя На сайт 2026.08

События и аналитика

Лента событий, поток в реальном времени, шаблоны, аналитика и очистка истории.

Раздел описывает всё, что относится к событиям контрол-плейна: чтение ленты, живой поток SSE, шаблоны оформления событий, аналитические запросы и дашборды, вложения (доказательства) и асинхронную очистку истории.

Все примеры на странице предполагают, что переменные окружения уже заданы:

export BASE="https://cctv.example.com"

# Токен сессии: обычный вход логином и паролем.
export TOKEN=$(curl -sS "$BASE/api/v1/auth/login" \
  -H 'Content-Type: application/json' \
  -d '{"login":"admin","password":"<пароль>"}' \
  | python3 -c 'import sys, json; print(json.load(sys.stdin)["token"])')

Общие правила аутентификации, пагинации и кодов ошибок — на странице Введение и аутентификация.

Модель события#

Событие — это зафиксированный в базе факт: движение в кадре, пересечение линии, потеря связи с камерой, недоступность ноды. У события всегда есть время occurred_at, организация org_id и, как правило, камера (camera_id) и нода (node_id).

Ключевые поля:

Поле Тип Смысл
detector строка идентификатор источника-детектора, например module.motion. Задаётся производителем события
template_id uuid | null шаблон, по которому событие озаглавлено, отрисовано и проиндексировано для поиска
detector_title JSON готовый заголовок: строка либо объект локализаций
category camera | system событие про камеру либо про саму систему (нода, хранилище, конфигурация)
severity debug | info | warning | error | critical | null важность; в фильтрах допускается и число 0..4. debug (0) — служебный уровень, по умолчанию скрыт (см. фильтр severity)
confidence число уверенность детектора, если он её сообщает
interval bool признак интервального события — см. ниже
ended_at RFC 3339 | null момент закрытия интервала; null — интервал ещё открыт
subject_type, subject_ref строка объект события: номер, метка, идентификатор распознанного субъекта
list_values массив значения полей, объявленных шаблоном как «поля списка» — то, что видно в строке ленты
payload объект исходная полезная нагрузка от детектора
attachments JSON ссылки на вложения-доказательства (кадры, короткие ролики)
starred bool пометка «в избранное», ставится любым видящим событие пользователем
ext_id строка | null идентификатор события во внешней системе, если она его прислала

Мгновенные и интервальные события#

Мгновенное событие — точка на шкале времени: interval: false, ended_at всегда пуст. Типичный пример — сработка детектора движения.

Интервальное событие — эпизод, который сначала открывается, а позже закрывается. Пока ended_at пуст, эпизод считается открытым и его видно в фильтре open=true.

Пример из практики: камера перестала отвечать — система открывает интервальное событие «камера недоступна»; камера вернулась в строй — то же самое событие закрывается штампом ended_at. Длительность эпизода = ended_at − occurred_at, именно её считают аналитические меры total_duration_seconds и avg_duration_seconds.

Закрывает интервал либо тот же модуль, который его открыл (по своему модульному токену), либо администратор организации — запросом PATCH /api/v1/events/{id}.

Событие без закрытия остаётся открытым навсегда

Если модуль-производитель удалён вместе со своим токеном, его открытые интервалы уже некому закрыть автоматически. Закройте их вручную запросом PATCH /api/v1/events/{id} от имени администратора организации.

Кто создаёт события#

  • Сам контрол-плейн — системные события (category: system): нода недоступна, камера offline, давление на хранилище доказательств.
  • Камеры по ONVIF — если у камеры включён приём ONVIF-событий.
  • Внешние аналитические модули — пакетной загрузкой POST /api/v1/events под модульным токеном (не сессией). Управление токенами описано на странице Мониторинг и служебное.

Вложения-доказательства#

Вложение — это байты доказательства (кадр, короткий ролик), сохранённые в хранилище доказательств контрол-плейна. Событие ссылается на вложение по ref (uuid). Размер одного вложения — до 16 МиБ.

Ссылка на вложение никогда не является пропуском: каждый запрос заново проверяет сессию и права на камеру события. Подписанных URL и параметров ?token= здесь нет принципиально.

Права и общие ограничения#

Чтение ленты доступно любому вошедшему пользователю, но список автоматически сужается правами на камеры: событие камеры, которой вы не видите, просто отсутствует в выдаче — это никогда не 403. Параметр ?org — это привилегия, а не подсказка: если не-суперадмин назовёт чужую организацию, ответ будет 403 {"error":"cross_org"}, а не молчаливый откат к своей.

Пространство событий защищено двумя отдельными шлюзами одновременных запросов:

Шлюз На какие запросы Предел Отказ
Поисковые слоты GET /events, GET /events/navigate, POST /events/reconcileтолько когда задан q 4 одновременно при стандартном пуле подключений к базе 429 {"error":"event_search_busy","retry_after":1}, заголовок Retry-After: 1
Слоты выдачи GET /events/{id}, GET /attachments/{ref}, GET /attachments/{ref}/preview 4 одновременно при стандартном пуле подключений к базе 429 {"error":"event_response_busy","retry_after":1}, заголовок Retry-After: 1

Слот выдачи удерживается телом ответа и освобождается только по концу передачи или разрыву соединения. Тело отдаётся кадрами по 64 КиБ, поэтому медленный клиент не блокирует остальных.

Универсальные ошибки не повторяются в каждой таблице

Любой эндпоинт этой страницы может вернуть 401 {"error":"unauthorized"} (нет или истёк токен), 403 {"error":"forbidden"} (роль не подходит под шлюз) и 503 {"error":"db_unavailable"} (недоступна PostgreSQL). В таблицах ниже перечислены только ошибки, специфичные для запроса.

Лента событий#

GET /api/v1/events#

Любой вошедший — выдача сужается правами на камеры.

Назначение. Одна страница событий от новых к старым по заданному фильтру плюс курсор для подписки на живой поток. Курсор снимается до запроса к базе, поэтому событие, записанное во время выборки, не потеряется при переходе на SSE.

Параметры запроса

Параметр Тип По умолчанию Описание
org uuid сужение для суперадмина; для остальных чужая организация — 403 cross_org
from RFC 3339 нижняя граница включительно
to RFC 3339 верхняя граница исключительно; from >= to400 bad_event_range
camera uuid одна камера
tag uuid тег камеры
detector строка точный идентификатор либо маска с хвостовой звёздочкой: module.*
severity строка debug | info | warning | error | critical либо число 0..4. Смысл — «не ниже указанной». Особое значение none выбирает события без важности. Без параметра выдача — только «видимые» события: уровень debug (0) в неё не входит и возвращается только при явном фильтре, который достигает нуля (severity=debug, точная пара severity=debug&severity_max=0 или severity_max=0 без нижней границы)
severity_max строка верхняя граница тем же словарём
starred bool только избранные
open bool только незакрытые интервалы
category строка camera | system; иное → 400 bad_category
q строка универсальный поиск по тексту события и по типизованным полям шаблона: поле:значение, фразы в кавычках. В каждом свободном слове должно быть не менее двух буквенно-цифровых символов
after_ts RFC 3339 курсор, часть 1
after_id uuid курсор, часть 2 — передаются только вместе
limit целое 100 ограничивается диапазоном 1..500

Пример запроса

# События категории «камера» важностью не ниже warning за сутки, страница по 100 строк.
curl -sS -G "$BASE/api/v1/events" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode 'from=2026-08-17T00:00:00Z' \
  --data-urlencode 'to=2026-08-18T00:00:00Z' \
  --data-urlencode 'category=camera' \
  --data-urlencode 'severity=warning' \
  --data-urlencode 'limit=100'

Пример ответа

{
  "rows": [
    {
      "id": "<UUID>",
      "occurred_at": "2026-08-17T14:03:11Z",
      "ended_at": null,
      "org_id": "<UUID>",
      "camera_id": "<UUID>",
      "camera_name": "Въезд, столб 3",
      "node_id": "<UUID>",
      "node_name": "node-1",
      "detector": "system.camera_offline",
      "detector_title": "Камера недоступна",
      "interval": true,
      "category": "camera",
      "template_id": "<UUID>",
      "severity": "warning",
      "confidence": 1.0,
      "subject_type": null,
      "subject_ref": null,
      "starred": false,
      "ext_id": null
    }
  ],
  "next_ts": "2026-08-17T14:03:11Z",
  "next_id": "<UUID>",
  "stream_cursor": "g:41927"
}

Следующую страницу запрашивайте с after_ts=<next_ts>&after_id=<next_id>. Признак конца — next_ts: null.

Ошибки

Код Тело Причина
400 bad_event_cursor передана только одна половина курсора
400 bad_event_range from >= to
400 bad_severity / bad_severity_range неизвестная важность; severity=none вместе с severity_max; нижняя граница выше верхней
400 bad_category категория не camera и не system
400 event_search_too_short в слове поиска меньше двух буквенно-цифровых символов
422 {"error":"event_search_too_broad","candidate_limit":10000} поиск отобрал более 10 000 кандидатов — сузьте фильтр
429 {"error":"event_search_busy","retry_after":1} заняты все поисковые слоты (по умолчанию 4)
503 {"error":"event_search_deadline","retry_after":1} поиск упёрся в тайм-аут запроса к базе
403 cross_org чужая организация в ?org

Примечания. stream_cursor передавайте в /api/v1/events/stream как есть. Курсор аудита сюда не подходит: у лент независимые последовательности, а курсор событий может нести префикс дня.

GET /api/v1/events/{id}#

Любой вошедший — событие вне ваших прав отдаётся как 404.

Назначение. Полная карточка одного события: строка ленты, документ назначенного шаблона и отрисованные по шаблону поля детализации.

Параметры пути. id — uuid события. Идентификатор должен иметь форму секционированного (UUIDv7-подобного) значения; иначе 404 без обращения к базе.

Пример запроса

curl -sS "$BASE/api/v1/events/<UUID>" \
  -H "Authorization: Bearer $TOKEN"

Пример ответа

{
  "event": { "id": "<UUID>", "occurred_at": "2026-08-17T14:03:11Z", "detector": "module.motion" },
  "template": { "detector_match": "module.*", "title": "Движение" },
  "rendered": {
    "details": [
      { "key": "zone", "label": "Зона", "format": "text", "value_labels": null, "value": "Периметр" }
    ]
  }
}

Ответ отдаётся с Cache-Control: private, no-store. Каждое отрисованное значение ограничено 8 КиБ; слишком большое значение заменяется на {"truncated": true}.

Ошибки

Код Тело Причина
404 not_found события нет, либо оно вне ваших прав
410 archived_data_expired событие существовало, но его архивное поколение уже выведено из хранения
429 event_response_busy заняты все слоты выдачи (по умолчанию 4)
500 response_encode_failed не удалось собрать ответ

Примечания. Различайте 404 и 410: первое — «нет или не ваше», второе — «было, но доказательства истекли». Это разные операционные ситуации.

PATCH /api/v1/events/{id}#

Администратор — либо администратор организации события, либо тот самый модульный токен, который это событие создал. Другие сочетания — 403 forbidden.

Назначение. Закрыть открытый интервал, проставив ended_at.

Параметры пути. id — uuid события.

Тело запроса

Поле Тип Обязательно Описание
ended_at RFC 3339 да момент закрытия эпизода

Пример запроса

curl -sS -X PATCH "$BASE/api/v1/events/<UUID>" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"ended_at":"2026-08-17T14:41:00Z"}'

Пример ответа

{ "id": "<UUID>", "ended_at": "2026-08-17T14:41:00Z" }

Ошибки

Код Тело Причина
400 not_interval событие мгновенное, закрывать нечего
400 текст проверки времени ended_at раньше occurred_at либо слишком далеко в будущем
404 not_found нет такого события
409 already_closed интервал уже закрыт
410 archived_data_expired архивное поколение события выведено из хранения

POST /api/v1/events/{id}/star#

Любой вошедший — достаточно видеть событие.

Назначение. Пометить событие как избранное. Пометка хранится в самой записи события, поэтому её видят все, кому это событие доступно, — она не персональная.

Параметры пути. id — uuid события. Тело запроса. Отсутствует.

curl -sS -X POST "$BASE/api/v1/events/<UUID>/star" \
  -H "Authorization: Bearer $TOKEN"
{ "id": "<UUID>", "starred": true }

Ошибки: 404 not_found; 410 archived_data_expired.

DELETE /api/v1/events/{id}/star#

Любой вошедший

Назначение. Снять пометку «избранное».

Параметры пути. id — uuid события. Тело запроса. Отсутствует.

curl -sS -X DELETE "$BASE/api/v1/events/<UUID>/star" \
  -H "Authorization: Bearer $TOKEN"
{ "id": "<UUID>", "starred": false }

Ошибки: 404 not_found; 410 archived_data_expired.

GET /api/v1/events/adjacent#

Любой вошедший

Назначение. Переход к предыдущему или следующему событию одной камеры относительно позиции воспроизведения — кнопки «предыдущее / следующее событие» в плеере архива. Фильтров ленты здесь нет.

Параметры запроса

Параметр Тип Обязательно Описание
camera uuid да камера
anchor RFC 3339 да текущая позиция воспроизведения
anchor_id uuid нет если передан, сравнение строгое — одновременные события остаются достижимы; без него совпадение по времени считается включительным
direction строка да previous или next; иное → 400 bad_direction
excluded_detectors строка нет JSON-массив строк в URL-кодировке с точными идентификаторами детекторов, которые надо скрыть. До 64 элементов, каждый 1..256 байт

Пример запроса

curl -sS -G "$BASE/api/v1/events/adjacent" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode 'camera=<UUID>' \
  --data-urlencode 'anchor=2026-08-17T14:00:00Z' \
  --data-urlencode 'direction=next' \
  --data-urlencode 'excluded_detectors=["module.motion","module.line"]'

Пример ответа

{ "event": { "id": "<UUID>", "occurred_at": "2026-08-17T14:03:11Z", "detector": "module.line" } }

Здесь возвращается полная строка события, а не только ключ. Если следующего события нет — {"event": null}. Ответ помечен Cache-Control: no-store.

Ошибки

Код Тело Причина
400 bad_direction направление не previous и не next
400 bad_excluded_detectors не JSON-массив, больше 64 элементов, пустой или неканонический идентификатор

Примечания. Повторение параметра (?excluded_detectors=a&excluded_detectors=b) не работает — разбор строки запроса не собирает повторы в список. Форма JSON-массива обязательна. Камера вне ваших прав неотличима от «событий больше нет».

GET /api/v1/events/navigate#

Любой вошедший

Назначение. Шаг на одну строку вперёд или назад внутри текущего результата ленты — стрелки «предыдущее / следующее» в карточке события. Сохраняются все фильтры, полнотекстовый поиск и права.

Параметры запроса. Все параметры GET /api/v1/events плюс три обязательных:

Параметр Тип Обязательно Описание
anchor RFC 3339 да occurred_at текущей строки
anchor_id uuid да id текущей строки
direction строка да previous | next; иное → 400 bad_direction

Пример запроса

curl -sS -G "$BASE/api/v1/events/navigate" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode 'category=camera' \
  --data-urlencode 'severity=warning' \
  --data-urlencode 'anchor=2026-08-17T14:03:11Z' \
  --data-urlencode 'anchor_id=<UUID>' \
  --data-urlencode 'direction=next'

Пример ответа

{ "event": { "id": "<UUID>", "occurred_at": "2026-08-17T13:58:02Z" } }

Возвращается только ключ: навигация — это поиск соседа, а не вторая загрузка карточки. Тело соседа берите запросом GET /api/v1/events/{id}, когда оператор действительно его выбрал. Если соседа нет, приходит {"event": null}.

Ошибки: 400 bad_direction и все проверки фильтров ленты; 422 event_search_too_broad; 429 event_search_busy; 403 cross_org.

POST /api/v1/events/reconcile#

Любой вошедший

Назначение. Мост между живым потоком и режимом поиска. Кадры SSE не могут доказать произвольный текстовый предикат, поэтому клиент присылает ключи событий, пришедшие по потоку, и получает обратно ровно те из них, что действительно попадают под текущий фильтр — вместо повторной загрузки страницы.

Параметры запроса. Те же, что у GET /api/v1/events, кроме after_ts / after_id — они запрещены (400 event_reconcile_cursor_not_allowed). limit игнорируется.

Тело запроса

Поле Тип По умолчанию Описание
candidates массив объектов [] до 512 элементов вида {"id":"<UUID>","occurred_at":"<RFC 3339>"}; дубликаты схлопываются

Разброс времён кандидатов после отбрасывания выходящих за from/to не должен превышать 31 день. Максимальный размер тела — 96 КиБ.

Пример запроса

curl -sS -X POST "$BASE/api/v1/events/reconcile?category=camera&q=zone%3Aperimeter" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"candidates":[{"id":"<UUID>","occurred_at":"2026-08-17T14:03:11Z"}]}'

Пример ответа

{ "rows": [ { "id": "<UUID>", "occurred_at": "2026-08-17T14:03:11Z", "detector": "module.motion" } ],
  "next_ts": null, "next_id": null }

Поля stream_cursor здесь нет.

Ошибки

Код Тело Причина
400 event_reconcile_too_many_candidates больше 512 кандидатов
400 event_reconcile_cursor_not_allowed переданы after_ts / after_id
400 event_reconcile_span_too_wide разброс времён кандидатов больше 31 дня
400 любые проверки фильтров ленты некорректный фильтр
413 тело больше 96 КиБ
422 event_search_too_broad слишком широкий поиск
429 event_search_busy заняты поисковые слоты (по умолчанию 4)

Примечания. Отсутствующий кандидат и кандидат вне ваших прав намеренно неразличимы — оба просто не попадают в rows.

Поток событий в реальном времени#

GET /api/v1/events/stream#

Любой вошедший — поток фильтруется правами на камеры.

Назначение. Живая лента событий по протоколу Server-Sent Events (SSE). Это единственный правильный способ «следить» за событиями: опрашивать GET /api/v1/events в цикле не нужно.

Параметры запроса

Параметр Тип По умолчанию Описание
org uuid сужение для суперадмина
last_event_id строка точка возобновления, запасной вариант для клиентов, которые не умеют слать заголовки

Точка возобновления задаётся заголовком Last-Event-ID; заголовок всегда важнее параметра запроса. Значение курсора — это либо g:<номер> (глобальная последовательность), либо d<день-очистки>:<номер>, когда действует отсечка по дню очистки. Не собирайте курсор сами: берите stream_cursor из ответа GET /api/v1/events дословно.

Пример запроса

# -N отключает буферизацию curl: кадры видны сразу.
curl -N -sS "$BASE/api/v1/events/stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept: text/event-stream' \
  -H 'Last-Event-ID: g:41927'

Пример потока

: delivery=replay
id: g:41928
data: {"seq":41928,"id":"<UUID>","occurred_at":"2026-08-17T14:03:11Z","org_id":"<UUID>","camera_id":"<UUID>","camera_name":"Въезд, столб 3","node_id":"<UUID>","node_name":"node-1","detector":"system.camera_offline","detector_title":"Камера недоступна","category":"camera","severity":2,"template_id":"<UUID>","interval":true,"ended_at":null,"starred":false,"updated":false,"deleted":false}

id: g:41931
data: {"seq":41931,"id":"<UUID>","occurred_at":"2026-08-17T14:03:11Z","ended_at":"2026-08-17T14:41:00Z","detector":"system.camera_offline","updated":true,"deleted":false}

:

Как читать этот вывод:

  • Имени события нет — поле event: не передаётся, поэтому в браузере срабатывает обработчик message по умолчанию.
  • id: — это курсор, его же браузер сам вернёт в Last-Event-ID при переподключении.
  • data: — одна сводка события в JSON: seq, id, occurred_at, ended_at, org_id, camera_id, camera_name, node_id, node_name, detector, detector_title, category, severity, template_id, interval (только в кадрах создания и полного изменения), confidence, subject_type, subject_ref, ext_id, list_values, starred, updated, deleted, reconcile_required (отсутствует, когда false).
  • Поток не фильтрует по важности — в кадрах приходят и события уровня debug (severity: 0). Клиент, показывающий выдачу «любой видимой» важности, обязан сам не отображать кадры с severity = 0, пока пользователь явно не выбрал уровень «Отладка» (штатные интерфейсы так и делают).
  • updated: true — это изменение уже показанной строки (например, интервал закрылся). Обновите существующую строку, а не добавляйте новую.
  • deleted: true — надгробие: строка удалена, уберите её из списка.
  • Строки, начинающиеся с двоеточия, — комментарии. : delivery=replay помечает кадры, доигранные из кольца после переподключения. Одинокое двоеточие раз в 15 секунд — сигнал «соединение живо».
  • : resync-required — терминальный комментарий: поток завершается. Так бывает, если курсор испорчен или указывает в будущее, нужный номер уже вытеснен из кольца (глубина повтора — 512 событий), сменился день очистки или подписчик отстал. Перечитайте GET /api/v1/events и подпишитесь заново с новым stream_cursor.

Ошибки

Код Тело Причина
403 cross_org чужая организация в ?org
429 {"error":"live_stream_capacity","scope":"global"|"principal","retry_after":2} исчерпан лимит живых потоков: 1024 на процесс и 32 на одного пользователя

Примечания.

  • Поток обрывается по истечении срока действия вашей сессии — тело SSE не продлевает токен.
  • Поток также обрывается в полночь UTC: меняется маршрут дня очистки. Это штатное поведение, клиент обязан переподключаться.
  • reconcile_required: true означает, что строка в базе полная, но её поля не поместились в 8-килобайтный конверт уведомления PostgreSQL. Перечитайте это событие (GET /api/v1/events/{id} или POST /api/v1/events/reconcile); не показывайте усечённый кадр как достоверный.
  • Слот живого потока освобождается по закрытию тела ответа. Клиент, бросивший поток, но не закрывший сокет, продолжает занимать слот.

Очистка истории событий#

Очистка удаляет события и их вложения безвозвратно. Порядок работы всегда один и тот же: preview → проверка чисел → purge → опрос задания.

Очистка необратима — всегда начинайте с preview

POST /api/v1/events/purge удаляет события вместе с байтами доказательств. Восстановить их невозможно ни из базы, ни из хранилища. Сначала выполните POST /api/v1/events/purge/preview с тем же телом и убедитесь, что числа совпадают с ожиданиями. Особенно внимательно проверяйте запросы без org_id: пустое поле означает все организации, а не «текущую».

POST /api/v1/events/purge/preview#

Суперадмин

Назначение. Посчитать, что именно удалит выбранный фильтр, ничего не удаляя.

Тело запроса. Одинаково для preview и для самой очистки; у всех полей есть значения по умолчанию.

Поле Тип По умолчанию Описание
event_ids массив uuid [] точечный выбор, до 1000 идентификаторов; сортируются и дедуплицируются на сервере
org_id uuid | null null nullвсе организации
from RFC 3339 | null null нижняя граница
to RFC 3339 | null null верхняя граница; from >= to400 bad_time_range
camera_id uuid | null null одна камера
detector строка | null null до 256 символов; хвостовая * — префиксная маска
severity_min целое | null null минимальная важность 0..4. Без неё удаление по фильтрам затрагивает только «видимые» события (уровень debug не удаляется); явный точечный выбор event_ids достигает и отладочных строк
starred bool false true — только избранные
category строка | null null camera | system
as_of RFC 3339 текущий момент UTC момент среза выборки; будущее значение опускается до «сейчас»

Пример запроса

curl -sS -X POST "$BASE/api/v1/events/purge/preview" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "org_id": "<UUID>",
        "to": "2026-06-01T00:00:00Z",
        "category": "camera"
      }'

Пример ответа

{
  "spec": {
    "event_ids": [],
    "org_id": "<UUID>",
    "from": null,
    "to": "2026-06-01T00:00:00Z",
    "camera_id": null,
    "detector": null,
    "severity_min": null,
    "starred": false,
    "category": "camera",
    "as_of": "2026-08-18T09:12:44Z"
  },
  "preview": { "events": 128455, "attachments": 96210, "attachment_bytes": 41203998720 }
}

Поле spec — это нормализованная заявка вместе с вычисленным as_of. Отправляйте её же в POST /api/v1/events/purge, чтобы удалить ровно то, что посчитано.

Ошибки

Код Тело Причина
400 event_purge_selection_too_large больше 1000 значений в event_ids
400 bad_time_range from >= to
400 bad_detector детектор длиннее 256 символов
400 bad_category категория не camera и не system

Примечания. Считаются только события в открытых корзинах хранения; уже закрытые архивные поколения этим фильтром не выбираются.

POST /api/v1/events/purge#

Суперадмин

Назначение. Поставить очистку в очередь. Запрос ничего не удаляет синхронно: он заново считает предпросмотр, отказывается от пустой выборки, создаёт задание очистки и пишет запись аудита event.delete.

Тело запроса. Точно такое же, как у preview.

Пример запроса

curl -sS -X POST "$BASE/api/v1/events/purge" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "org_id": "<UUID>",
        "to": "2026-06-01T00:00:00Z",
        "category": "camera",
        "as_of": "2026-08-18T09:12:44Z"
      }'

Пример ответа202 Accepted:

{
  "id": "<UUID>",
  "kind": "events",
  "org_id": "<UUID>",
  "state": "queued",
  "preview": { "events": 128455, "attachments": 96210, "attachment_bytes": 41203998720 },
  "progress": {},
  "created_at": "2026-08-18T09:13:02Z",
  "finished_at": null,
  "last_error": null
}

Ошибки

Код Тело Причина
400 те же проверки, что у preview некорректная заявка
409 event_purge_empty под фильтр не попало ни одного события

Примечания. Момент as_of замораживает выборку: события, появившиеся после нажатия кнопки, под очистку не попадут. Идентификатор задания из ответа — единственный способ узнать результат, других списков заданий нет; сохраните его.

GET /api/v1/cleanup-jobs/{id}#

Суперадмин

Назначение. Опросить асинхронное задание очистки. Через этот эндпоинт отслеживаются и очистка событий, и очистка архива организации.

Параметры пути. id — uuid задания из ответа 202.

Пример запроса

# Опрос до завершения: раз в 5 секунд, пока не проставится finished_at.
JOB="<UUID>"
until curl -sS "$BASE/api/v1/cleanup-jobs/$JOB" \
        -H "Authorization: Bearer $TOKEN" | tee /dev/stderr | grep -q '"finished_at":"'; do
  sleep 5
done

Пример ответа

{
  "id": "<UUID>",
  "kind": "events",
  "org_id": "<UUID>",
  "state": "finished",
  "preview": { "events": 128455, "attachments": 96210, "attachment_bytes": 41203998720 },
  "progress": { "events": 128455, "attachments": 96210 },
  "created_at": "2026-08-18T09:13:02Z",
  "finished_at": "2026-08-18T09:31:47Z",
  "last_error": null
}

Ошибки

Код Тело Причина
404 not_found нет задания с таким идентификатором

Примечания. Признак завершения — непустое finished_at. Неудачный прогон виден по непустому last_error. Списка заданий не существует: идентификатор надо сохранять при постановке.

Шаблоны событий#

Шаблон решает, как события конкретного детектора называются, что видно в строке ленты, по каким полям они ищутся и агрегируются и как выглядит карточка. Шаблон привязывается к детекторам маской detector_match (например module.*).

Шаблоны бывают двух видов:

  • шаблоны организацииorg_id задан, system: false; администратор организации редактирует их полностью;
  • системные шаблоныorg_id: null, system: true; поставляются с системой, видны всем администраторам и правятся только суперадмином и только в узких пределах (см. PATCH).

Документ шаблона проверяется мягко: незнакомые ключи сохраняются и возвращаются в поле unknown_fields, жёстко проверяются только системные поля.

Все эндпоинты раздела — Администратор.

Общая таблица ошибок проверки документа (одинакова для создания, изменения и validate):

Код Тело Причина
400 bad_doc документ не разбирается как шаблон
400 template_doc_too_large документ превышает предельный размер
400 template_title_invalid title не строка и не объект локализаций либо слишком велик
400 detector_match_required detector_match отсутствует или пуст
400 detector_match_too_long detector_match длиннее 256 символов
400 list_fields_too_many, list_field_bad_key, list_field_bad_source, list_field_bad_metadata объявление полей строки ленты
400 attachment_fields_too_many, attachment_field_bad_source, attachment_field_bad_metadata объявление полей вложений
400 detail_layout_too_many, detail_layout_block_too_large раскладка карточки
400 coalesce_sources_too_many, coalesce_source_invalid, bad_interval_timeout склейка событий и параметры интервалов
400 search_field_bad_source объявление полей поиска и аналитики

GET /api/v1/templates#

Администратор

Назначение. Список шаблонов, видимых вызывающему: системные плюс шаблоны его организации.

Параметры запроса

Параметр Тип По умолчанию Описание
org uuid своя организация сужение для суперадмина; чужая организация у остальных — 403 cross_org
after uuid курсор: next из предыдущей страницы
limit целое 500 диапазон 1..500

Параметр q в этом обработчике не используется.

curl -sS "$BASE/api/v1/templates?limit=200" \
  -H "Authorization: Bearer $TOKEN"
{
  "rows": [
    {
      "id": "<UUID>",
      "org_id": null,
      "name": "Движение",
      "detector_match": "module.motion",
      "version": 3,
      "doc": { "detector_match": "module.motion", "title": "Движение" },
      "system": true,
      "enabled": true,
      "unknown_fields": [],
      "updated_at": "2026-07-30T11:02:00Z"
    }
  ],
  "next": null
}

Ошибки: 403 cross_org.

POST /api/v1/templates#

Администратор

Назначение. Создать шаблон организации. Системные шаблоны через API не создаются: поле system здесь всегда false.

Тело запроса

Поле Тип Обязательно По умолчанию Описание
name строка да 1..128 символов после обрезки пробелов
doc объект да документ шаблона; обязан содержать непустой detector_match
org_id uuid | null нет своя организация суперадмин может указать любую
curl -sS -X POST "$BASE/api/v1/templates" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "name": "Пересечение линии",
        "doc": {
          "detector_match": "module.line",
          "title": "Пересечение линии",
          "list_fields": [ { "key": "direction", "label": "Направление", "source": "payload.direction" } ]
        }
      }'

Ответ — 201 с созданной строкой шаблона.

Ошибки: общая таблица проверки документа; 400 bad_name; 403 cross_org; 409 max_event_templates_reached (исчерпана квота организации); 409 already_exists.

POST /api/v1/templates/validate#

Администратор

Назначение. Прогнать документ ровно через ту же проверку, что и при сохранении, ничего не записывая. Ни квоты, ни аудита, ни сброса кэшей — чистая проверка для редактора.

Тело запроса

Поле Тип Обязательно Описание
doc объект да документ шаблона
curl -sS -X POST "$BASE/api/v1/templates/validate" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"doc":{"detector_match":"module.*","title":"Аналитика"}}'
{ "valid": true, "detector_match": "module.*", "document_bytes": 184, "unknown_fields": [] }

Ответ существует только в виде valid: true: некорректный документ возвращается обычной типизованной ошибкой 400 — той же самой, что выдало бы сохранение.

Ошибки: общая таблица проверки документа.

GET /api/v1/templates/{id}#

Администратор

Назначение. Прочитать один шаблон. Системный шаблон доступен любому администратору, шаблон организации — только администратору этой организации.

Параметры пути. id — uuid шаблона.

curl -sS "$BASE/api/v1/templates/<UUID>" \
  -H "Authorization: Bearer $TOKEN"

Ошибки: 403 cross_org; 404 not_found.

PATCH /api/v1/templates/{id}#

Администратор — для системных шаблонов только суперадмин.

Назначение. Изменить шаблон.

Параметры пути. id — uuid шаблона.

Тело запроса — все поля необязательны:

Поле Тип Описание
name строка новое имя, 1..128 символов
doc объект новый документ (заменяется целиком)
enabled bool включён ли шаблон

Правила для системных шаблонов (system: true):

  • не-суперадмин получает 403 system_template_superadmin_only;
  • передача enabled или имени, отличного от текущего, — 400 system_template_fields_readonly;
  • поле doc обязательно — иначе 400 system_template_override_required;
  • в doc допустимы только ключи title и ignore_event, иначе 400 system_template_fields_readonly; неверные значения — 400 system_template_title_invalid либо 400 system_template_ignore_invalid.
curl -sS -X PATCH "$BASE/api/v1/templates/<UUID>" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"enabled":false}'

Ответ — 200 с обновлённой строкой шаблона; её version увеличивается.

Ошибки: общая таблица проверки документа; 400 bad_name; 403 cross_org; 403 system_template_superadmin_only; 404 not_found; 409 analytics_template_in_use — правка удаляет или меняет аналитическое поле, на которое всё ещё ссылается сохранённый отчёт или виджет.

DELETE /api/v1/templates/{id}#

Администратор

Назначение. Удалить шаблон организации.

Параметры пути. id — uuid шаблона. Тело запроса. Отсутствует.

curl -sS -X DELETE "$BASE/api/v1/templates/<UUID>" \
  -H "Authorization: Bearer $TOKEN"
{ "id": "<UUID>", "deleted": true }

Ошибки: 403 cross_org; 404 not_found; 409 system_template_readonly (системный шаблон удалить нельзя никогда); 409 analytics_template_in_use.

GET /api/v1/templates/{id}/matches#

Администратор

Назначение. Проверить маску: какие идентификаторы детекторов, встречавшиеся в недавних событиях, действительно попадают под detector_match этого шаблона. Инструмент редактора — на приём событий он не влияет.

Параметры пути. id — uuid шаблона.

Параметры запроса. Используется только org: системный шаблон разрешается по организации вызывающего (суперадмин может указать другую), шаблон организации — всегда по своей.

curl -sS "$BASE/api/v1/templates/<UUID>/matches" \
  -H "Authorization: Bearer $TOKEN"
{ "window_days": 7, "detectors": ["module.motion", "module.line"], "distinct": 2 }

Окно фиксировано — 7 дней, параметра для его изменения нет.

Ошибки: 403 cross_org; 404 not_found.

Аналитика по событиям#

Единая закрытая грамматика запросов к хранилищу событий. Запускать аналитику может любой вошедший; результат всегда ограничен правами вызывающего на камеры.

Поле в запросе адресуется парой {template_id, key} — путь к значению и его тип берутся из самого шаблона. Произвольный путь по JSON в запрос подставить нельзя.

GET /api/v1/event-analytics/catalog#

Любой вошедший

Назначение. Каталог доступной для запросов поверхности: какие шаблоны существуют и какие типизованные поля они объявили пригодными для аналитики — с подписями, типами, единицами измерения и списками допустимых значений. Именно это редактор предлагает выбрать в качестве меры и группировки.

Параметры запроса. Используется только org (сужение для суперадмина).

curl -sS "$BASE/api/v1/event-analytics/catalog" \
  -H "Authorization: Bearer $TOKEN"
{
  "templates": [
    { "id": "<UUID>", "org_id": null, "name": "Движение",
      "detector_match": "module.motion", "title": "Движение" }
  ],
  "fields": [
    { "template_id": "<UUID>", "template_name": "Движение", "detector_match": "module.motion",
      "key": "zone", "label": "Зона", "type": "enum", "unit": null,
      "enum_values": ["Периметр", "Склад"] }
  ]
}

Возвращаются только включённые шаблоны, системные первыми, не более 512 штук.

Ошибки: 403 cross_org.

Примечания. Каталог содержит только метаданные представления и безопасен для любого читателя — полные документы шаблонов (доступные лишь администраторам) здесь не раскрываются.

POST /api/v1/event-analytics/query#

Любой вошедший

Назначение. Выполнить один аналитический вопрос за конкретный интервал времени.

Тело запроса

Поле Тип Обязательно По умолчанию Описание
org_id uuid | null нет своя организация суперадмин может указать любую
from RFC 3339 да начало интервала
to RFC 3339 да конец интервала; from >= to400 analytics_bad_range
timezone строка нет UTC имя зоны IANA; неизвестное → 400 analytics_bad_timezone
version целое нет 1 версия грамматики
measure строка да count_events, unique_cameras, open_events, avg_confidence, total_duration_seconds, avg_duration_seconds, field_count, field_sum, field_avg, field_min, field_max
measure_field объект | null для мер field_* {"template_id":"<UUID>","key":"…"}
group_by строка нет none none, detector, camera, node, severity, category, field
group_field объект | null при group_by: field та же пара {template_id, key}
bucket строка нет none none, hour, day — шаг временной шкалы
semantics строка нет started started — по времени начала события; active — по пересечению с интервалом. active несовместимо с bucket
filters объект нет {} см. ниже
top_limit целое нет 12 сколько групп вернуть

Поля filters: detectors[], camera_ids[], tag_ids[], node_ids[], severity_min (0..4), severity_max (0..4), category, starred, open, а также fields[] — список условий вида {"field":{"template_id":"<UUID>","key":"zone"},"op":"eq","value":"Периметр"}, где op — одно из eq, neq, gt, gte, lt, lte, exists, not_exists.

Без явной границы, достигающей нуля, агрегаты считают только «видимые» события — уровень debug не раздувает счётчики; его срез запрашивается явно: "filters": { "severity_min": 0, "severity_max": 0 }.

curl -sS -X POST "$BASE/api/v1/event-analytics/query" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "from": "2026-08-11T00:00:00Z",
        "to":   "2026-08-18T00:00:00Z",
        "timezone": "Europe/Moscow",
        "measure": "count_events",
        "group_by": "camera",
        "bucket": "day",
        "top_limit": 10,
        "filters": { "category": "camera", "severity_min": 2 }
      }'
{
  "query_fingerprint": "b91c0f7d…",
  "as_of": "2026-08-18T09:20:00Z",
  "from": "2026-08-11T00:00:00Z",
  "to": "2026-08-18T00:00:00Z",
  "timezone": "Europe/Moscow",
  "measure": "count_events",
  "measure_field": null,
  "group_by": "camera",
  "group_field": null,
  "bucket": "day",
  "points": [
    { "bucket": "2026-08-11T00:00:00Z", "key": "<UUID>", "label": "Въезд, столб 3",
      "value": 42.0, "sample_count": 42 }
  ],
  "truncated": false,
  "warnings": []
}

Ошибки

Код Тело Причина
400 analytics_bad_range, analytics_bad_timezone интервал или часовой пояс
400 analytics_version_unsupported неизвестная версия грамматики
400 analytics_top_limit top_limit вне допустимого диапазона
400 analytics_field_required мера field_* без measure_field
400 active_semantics_cannot_bucket semantics: active вместе с bucket
400 analytics_filter_too_large слишком большой блок фильтров
400 analytics_bad_detector, analytics_bad_severity, analytics_bad_category значения фильтров
400 analytics_field_bad_key, analytics_field_bad_filter, analytics_field_not_found, analytics_field_operator_type, analytics_field_filter_type ссылки на поля шаблона и операторы
400 analytics_custom_field_requires_org поле организации запрошено без указания организации
403 cross_org чужая организация

POST /api/v1/event-analytics/query-batch#

Любой вошедший

Назначение. Выполнить все вопросы дашборда одним запросом по одному согласованному срезу и получить курсор для живого обновления.

Тело запроса

Поле Тип Обязательно Описание
queries массив да от 1 до 32 элементов вида {"key":"<имя виджета>","query":{…}}; key — 1..128 символов
curl -sS -X POST "$BASE/api/v1/event-analytics/query-batch" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "queries": [
          { "key": "widget-1",
            "query": { "from": "2026-08-17T00:00:00Z", "to": "2026-08-18T00:00:00Z",
                       "measure": "count_events" } },
          { "key": "widget-2",
            "query": { "from": "2026-08-17T00:00:00Z", "to": "2026-08-18T00:00:00Z",
                       "measure": "unique_cameras", "group_by": "node" } }
        ]
      }'
{
  "results": [
    { "key": "widget-1", "result": { "measure": "count_events", "points": [] } },
    { "key": "widget-2", "error": "analytics_bad_range" }
  ],
  "as_of": "2026-08-18T09:20:00Z",
  "stream_cursor": "g:41927"
}

Ошибка отдельного запроса возвращается внутри results полем error, а не HTTP-ошибкой: пакет всё равно отвечает 200. Целиком запрос падает только на отказе базы (503) и на нарушении границ организации (403).

Ошибки

Код Тело Причина
400 analytics_bad_batch пусто, больше 32 запросов либо неверная длина key
403 cross_org хотя бы один запрос назвал чужую организацию

Примечания. stream_cursor снимается до выполнения аналитических запросов и глобально durable — передайте его в /api/v1/events/stream, и живые изменения точно состыкуются с этим срезом, даже если поток попадёт на другой процесс контрол-плейна.

Дашборды и виджеты#

Дашборд владеет упорядоченным набором виджетов. Виджет — это либо ссылка на сохранённый отчёт (см. Теги, мозаики, шаблоны), либо встроенное определение. Чтение — для любого вошедшего с учётом видимости, запись — только для администратора.

И дашборды, и виджеты используют оптимистическую блокировку: в PATCH обязательно передаётся revision, прочитанная последней; при расхождении — 409 stale_revision.

GET /api/v1/analytics-dashboards#

Любой вошедший — видимость ограничена, см. примечания.

Назначение. Список дашбордов, доступных вызывающему.

Параметры запроса. Используется только org (сужение для суперадмина).

curl -sS "$BASE/api/v1/analytics-dashboards" \
  -H "Authorization: Bearer $TOKEN"
{
  "rows": [
    { "id": "<UUID>", "org_id": "<UUID>", "name": "Периметр за неделю", "description": "",
      "timezone": "Europe/Moscow", "default_period": "last_7d", "live_enabled": true,
      "visibility": "org", "audience_user_ids": [], "created_by": "<UUID>",
      "revision": 4, "created_at": "2026-08-01T10:00:00Z", "updated_at": "2026-08-15T08:30:00Z" }
  ]
}

Ответ — простая обёртка без курсора.

Ошибки: 403 cross_org.

Примечания. Обычный пользователь (роль user) видит дашборды с visibility: "org" и дашборды с visibility: "users", в аудиторию которых он явно включён. Администратор организации видит все дашборды своей организации.

POST /api/v1/analytics-dashboards#

Администратор

Назначение. Создать дашборд. Виджеты добавляются отдельными запросами.

Тело запроса

Поле Тип Обязательно По умолчанию Описание
name строка да 1..128 символов
description строка нет "" до 1024 символов
timezone строка нет UTC зона IANA
default_period строка нет today today, last_24h, week, last_7d, month, last_30d
live_enabled bool нет true живое обновление по SSE
visibility строка нет org org, users, admins
audience_user_ids массив uuid нет [] имеет смысл только при visibility: "users", где обязан быть непустым; каждый идентификатор — не отключённый пользователь роли user из той же организации
org_id uuid | null нет своя организация суперадмин может указать любую
curl -sS -X POST "$BASE/api/v1/analytics-dashboards" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "name": "Периметр за неделю",
        "timezone": "Europe/Moscow",
        "default_period": "last_7d",
        "visibility": "org"
      }'

Ответ — 201 со строкой дашборда.

Ошибки

Код Тело Причина
400 analytics_bad_name, analytics_bad_timezone, analytics_bad_period, analytics_bad_visibility проверка полей
400 analytics_dashboard_audience_required, analytics_dashboard_audience_limit, analytics_dashboard_bad_audience аудитория при visibility: "users"
403 cross_org чужая организация
409 max_dashboards_reached, already_exists квота организации, совпадение имени

GET /api/v1/analytics-dashboards/{id}#

Любой вошедший

Назначение. Один дашборд вместе с виджетами и разрешённой аудиторией — то, что загружает страница дашборда. Отдельного эндпоинта для списка виджетов нет.

Параметры пути. id — uuid дашборда.

curl -sS "$BASE/api/v1/analytics-dashboards/<UUID>" \
  -H "Authorization: Bearer $TOKEN"
{
  "id": "<UUID>", "org_id": "<UUID>", "name": "Периметр за неделю",
  "timezone": "Europe/Moscow", "default_period": "last_7d", "live_enabled": true,
  "visibility": "org", "audience_user_ids": [], "revision": 4,
  "widgets": [
    { "id": "<UUID>", "dashboard_id": "<UUID>", "report_id": "<UUID>", "inline_definition": null,
      "title": "Событий по камерам", "visualization": "bar", "options": {}, "layout": {},
      "position": 0, "revision": 2,
      "created_at": "2026-08-01T10:05:00Z", "updated_at": "2026-08-15T08:30:00Z" }
  ],
  "audience_users": []
}

Ошибки: 404 not_found — дашборда нет либо он вам не виден.

PATCH /api/v1/analytics-dashboards/{id}#

Администратор

Назначение. Изменить метаданные, видимость или аудиторию дашборда.

Параметры пути. id — uuid дашборда.

Тело запроса. Обязательное поле revision (целое) плюс любые из name, description, timezone, default_period, live_enabled, visibility, audience_user_ids — правила проверки те же, что при создании.

curl -sS -X PATCH "$BASE/api/v1/analytics-dashboards/<UUID>" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"revision":4,"live_enabled":false}'

Ошибки: все проверки создания; 409 stale_revision; 409 analytics_dashboard_visibility — новая видимость раскрыла бы отчёт виджета аудитории шире, чем разрешает сам отчёт; 403 cross_org; 404 not_found.

DELETE /api/v1/analytics-dashboards/{id}#

Администратор

Назначение. Удалить дашборд; его виджеты удаляются каскадом.

Параметры пути. id — uuid дашборда. Тело запроса. Отсутствует.

curl -sS -X DELETE "$BASE/api/v1/analytics-dashboards/<UUID>" \
  -H "Authorization: Bearer $TOKEN"
{ "id": "<UUID>", "deleted": true }

Ошибки: 403 cross_org; 404 not_found.

POST /api/v1/analytics-dashboards/{id}/widgets#

Администратор

Назначение. Добавить виджет в дашборд.

Параметры пути. id — uuid дашборда.

Тело запроса

Поле Тип Обязательно По умолчанию Описание
report_id uuid | null одно из двух null ссылка на сохранённый отчёт
inline_definition объект | null одно из двух null встроенное определение отчёта
title строка да 1..128 символов
visualization строка да kpi, line, bar, donut, table
options объект нет {} настройки отображения
layout объект нет {} положение и размер плитки
position целое нет 0 порядковый номер

Ровно одно из report_id / inline_definition должно быть задано.

curl -sS -X POST "$BASE/api/v1/analytics-dashboards/<UUID>/widgets" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "report_id": "<UUID>",
        "title": "Событий по камерам",
        "visualization": "bar",
        "position": 0
      }'

Ответ — 201 со строкой виджета.

Ошибки

Код Тело Причина
400 analytics_widget_source не задан ни один источник либо заданы оба
400 analytics_report_not_found, analytics_report_visibility отчёт недоступен дашборду
400 analytics_bad_visualization, analytics_bad_options, analytics_bad_layout, analytics_bad_position проверка полей
404 not_found нет такого дашборда
409 analytics_widget_limit достигнут предел числа виджетов на дашборд

PATCH /api/v1/analytics-widgets/{id}#

Администратор

Назначение. Изменить один виджет. Маршрут адресуется по идентификатору виджета, а не через дашборд; права проверяются по владеющему дашборду.

Параметры пути. id — uuid виджета.

Тело запроса. Обязательное revision (целое) плюс любые из report_id, inline_definition, title, visualization, options, layout, position.

curl -sS -X PATCH "$BASE/api/v1/analytics-widgets/<UUID>" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"revision":2,"visualization":"line","position":1}'

Ошибки: все проверки создания виджета; 409 stale_revision; 403 cross_org; 404 not_found — виджета нет либо его дашборд вам не виден.

DELETE /api/v1/analytics-widgets/{id}#

Администратор

Назначение. Удалить один виджет.

Параметры пути. id — uuid виджета. Тело запроса. Отсутствует.

curl -sS -X DELETE "$BASE/api/v1/analytics-widgets/<UUID>" \
  -H "Authorization: Bearer $TOKEN"
{ "id": "<UUID>", "deleted": true }

Ошибки: 403 cross_org; 404 not_found.

Вложения#

GET /api/v1/attachments/{ref}#

Любой вошедший — права на камеру события проверяются заново на каждом запросе.

Назначение. Скачать вложение целиком.

Параметры пути. ref — uuid вложения (в форме секционированного идентификатора, иначе 404).

curl -sS "$BASE/api/v1/attachments/<UUID>" \
  -H "Authorization: Bearer $TOKEN" \
  -o attachment.bin

Ответ отдаётся с исходным Content-Type, точным Content-Length и заголовками Cache-Control: private, no-store, Pragma: no-cache; тело передаётся кадрами по 64 КиБ.

Ошибки

Код Тело Причина
404 not_found вложения нет либо оно вне ваших прав — существование не раскрывается
404 object_gone метаданные есть, но байтов в хранилище уже нет
410 archived_data_expired архивное поколение выведено из хранения
429 event_response_busy заняты все слоты выдачи (по умолчанию 4)

Примечания. Каждое успешное скачивание пишется в аудит как data.blob_download с параметрами {size, preview:false}. Подписанных ссылок нет: без заголовка Authorization файл не отдаётся никогда.

GET /api/v1/attachments/{ref}/preview#

Любой вошедший

Назначение. Маленькое производное превью вложения в JPEG — то, что рисуется в списке событий.

Параметры пути. ref — uuid вложения, тот же, что у полного скачивания.

curl -sS "$BASE/api/v1/attachments/<UUID>/preview" \
  -H "Authorization: Bearer $TOKEN" \
  -o preview.jpg

Ответ — 200 image/jpeg, Cache-Control: private, no-store, кадры по 64 КиБ.

Ошибки

Код Тело Причина
404 no_preview превью нет: тип не превьюируется, ещё не построено или построение не удалось — используйте полное скачивание
404 not_found, object_gone как у полного скачивания
410 archived_data_expired архивное поколение выведено из хранения
429 event_response_busy заняты слоты выдачи

Примечания. Превью видеовложения — это тоже маленький JPEG либо no_preview; видео целиком этот эндпоинт не отдаёт никогда. Построение превью асинхронно и идёт отдельным процессом, поэтому no_preview сразу после приёма события — нормальная ситуация, повторите запрос позже.