События и аналитика
Лента событий, поток в реальном времени, шаблоны, аналитика и очистка истории.
Раздел описывает всё, что относится к событиям контрол-плейна: чтение ленты, живой поток 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 >= to → 400 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 → опрос задания.
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 >= to → 400 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 >= to → 400 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 сразу после приёма события — нормальная ситуация, повторите запрос позже.