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

Мониторинг и служебное

Состояние флота, аудит действий, настройки контрол-плейна, версия и потребление ресурсов.

Раздел описывает служебную часть API контрол-плейна: телеметрию флота и самого процесса, журналы, аудит действий пользователей, настройки plane.toml через API, модульные токены для интеграций и редкие административные операции.

Примеры предполагают заданные переменные окружения:

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"])')

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

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

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

Контрол-плейн не отдаёт метрики Prometheus

У контрол-плейна нет эндпоинта /metrics. Экспозиция в формате Prometheus есть у стриминговой ноды — контрол-плейн отдаёт своё состояние только описанными здесь JSON-эндпоинтами. Как строить внешний мониторинг из того и другого, см. Мониторинг и метрики и раздел Что опрашивать для мониторинга ниже.

Телеметрия флота и процесса#

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

Три из пяти — потоки SSE. Общие для них правила:

  • кадры без имени события (event: не передаётся), id: — порядковый номер, data: — JSON;
  • точка возобновления задаётся заголовком Last-Event-ID, заголовок важнее одноимённого параметра запроса там, где параметр вообще поддерживается;
  • комментарий-пульс раз в 15 секунд;
  • общий лимит живых потоков: 1024 на процесс и 32 на одного пользователя. При исчерпании — 429 {"error":"live_stream_capacity","scope":"global"|"principal","retry_after":2} с заголовком Retry-After: 2;
  • если переданный Last-Event-ID больше текущего номера буфера, точка возобновления сбрасывается в ноль и поток начинается с полного повтора: нумерация живёт в процессе и обнуляется при перезапуске.

GET /api/v1/monitor/fleet/summary#

Суперадмин

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

Параметры. Отсутствуют.

curl -sS "$BASE/api/v1/monitor/fleet/summary" \
  -H "Authorization: Bearer $TOKEN"

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

{
  "seq": 8471,
  "sampled_at": "2026-08-18T09:45:02Z",
  "nodes_total": 4,
  "nodes_reachable": 4,
  "stream_samples_stale": 0,
  "streams_total": 512,
  "streams_live": 508,
  "streams_degraded": 3,
  "streams_failing": 1,
  "cameras_total": 300,
  "cameras_live": 296,
  "cameras_unavailable": 2,
  "cameras_degraded": 1,
  "cameras_failing": 1,
  "nodes_unlicensed": 0,
  "nodes_grace": 0
}
Поле Смысл
seq номер обхода; растёт монотонно
nodes_total, nodes_reachable всего нод и сколько ответило в этом обходе
stream_samples_stale активные ноды, у которых в этом обходе не удалось получить число потоков: их последний известный итог сохранён, но в разбивку live/degraded/failing не входит
streams_* потоки на стороне нод
cameras_* инвентарь контрол-плейна; камера с профилями MAIN+SUB считается один раз
nodes_unlicensed, nodes_grace счётчики для баннера лицензий; нода, от которой не получен сигнал о лицензии, не попадает ни в один из них

GET /api/v1/monitor/fleet/summary/stream#

Суперадмин

Назначение. Та же сводка живым потоком SSE — на каждый новый обход.

Параметры запроса. Отсутствуют. Возобновление — только заголовком Last-Event-ID.

curl -N -sS "$BASE/api/v1/monitor/fleet/summary/stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept: text/event-stream' \
  -H 'Last-Event-ID: 8471'
id: 8472
data: {"seq":8472,"sampled_at":"2026-08-18T09:45:12Z","nodes_total":4,"nodes_reachable":4,"streams_total":512,"streams_live":509,"streams_degraded":3,"streams_failing":0,"cameras_total":300,"cameras_live":297,"nodes_unlicensed":0,"nodes_grace":0}

:

Текущая сводка повторяется один раз, если её seq новее точки возобновления, дальше идут живые обходы.

Ошибки: 429 live_stream_capacity.

GET /api/v1/monitor/nodes#

Суперадмин

Назначение. Поток телеметрии нод (SSE): достижимость, системные показатели и показатели хранилищ. Это не список нод — метаданные (имена, адреса, состояние) читаются один раз из GET /api/v1/nodes, см. Ноды и хранилища; сюда приходят только измерения.

Параметры запроса. Отсутствуют. Возобновление — только заголовком Last-Event-ID (значение разбирается как целое; неразбираемое считается нулём).

curl -N -sS "$BASE/api/v1/monitor/nodes" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept: text/event-stream'
id: 90218
data: {"seq":90218,"node_id":"<UUID>","attempted_at":"2026-08-18T09:45:12Z","last_seen_at":"2026-08-18T09:45:12Z","reachable":true,"system_telemetry":{"cpus":16,"proc_cpu_cores":3.8,"host_cpu_busy_pct":27.4,"mem_total_mb":64256,"mem_available_mb":38110,"rss_mb":4120,"stream_counts":{"live":128}},"storage_telemetry":[{"name":"dvr1","total_bytes":4000787030016,"avail_bytes":1480000000000,"write_iops":320,"identity_healthy":true}],"errors":[]}

:
Поле Тип Смысл
seq целое номер кадра, он же id:
node_id uuid нода
attempted_at RFC 3339 когда обходчик пытался опросить ноду
last_seen_at RFC 3339 | null когда нода отвечала в последний раз
reachable bool ответила ли нода в этом обходе
system_telemetry объект | null показатели хоста и процесса ноды: cpus, proc_cpu_cores, host_cpu_busy_pct, mem_total_mb, mem_available_mb, rss_mb, счётчики потоков stream_counts
storage_telemetry массив | null по одному описанию на хранилище ноды: name, total_bytes, avail_bytes, write_iops, признаки исправности идентичности диска
errors массив строк что именно не удалось в этом обходе

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

Ошибки: 429 live_stream_capacity.

GET /api/v1/monitor/plane#

Суперадмин

Назначение. История потребления ресурсов самим процессом контрол-плейна: последние 900 измерений с шагом 2 секунды, то есть окно в 30 минут.

Параметры. Отсутствуют.

curl -sS "$BASE/api/v1/monitor/plane" \
  -H "Authorization: Bearer $TOKEN"
{
  "cadence_secs": 2,
  "retention_points": 900,
  "samples": [
    {
      "seq": 41220,
      "sampled_at_ms": 1755509102000,
      "uptime_s": 862144,
      "rss_mb": 214,
      "proc_cpu_cores": 0.42,
      "cpus": 16,
      "host_cpu_busy_pct": 12.7,
      "mem_total_mb": 64256,
      "mem_available_mb": 49110,
      "open_fds": 812,
      "db_pool_max": 16,
      "db_pool_size": 16,
      "db_pool_idle": 13,
      "db_pool_in_use": 3
    }
  ]
}

Примечания. proc_cpu_cores — это доля ядер по разнице процессорного времени, а не мгновенный %cpu из выборки. Значение можно использовать напрямую: 0.42 означает «процесс потребляет 0,42 ядра». Пара db_pool_in_use / db_pool_max показывает, насколько плотно занят пул подключений к PostgreSQL.

GET /api/v1/monitor/plane/stream#

Суперадмин

Назначение. Те же измерения живым потоком — по одному кадру каждые 2 секунды.

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

Параметр Тип По умолчанию Описание
last_event_id целое точка возобновления; заголовок Last-Event-ID важнее
curl -N -sS "$BASE/api/v1/monitor/plane/stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept: text/event-stream'

Всё, что новее точки возобновления, сначала повторяется из кольца, затем идёт живая выдача.

Ошибки: 429 live_stream_capacity.

Журналы контрол-плейна#

Контрол-плейн держит собственное кольцо операционных записей: 4096 записей, только в памяти, на диск не пишется и перезапуск не переживает. Оба эндпоинта — только для суперадмина.

Журнал ноды — это другой маршрут, GET /api/v1/nodes/{id}/logs, см. Ноды и хранилища.

GET /api/v1/logs/plane#

Суперадмин

Назначение. Страница отфильтрованных записей кольца плюс текущая политика захвата.

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

Параметр Тип По умолчанию Описание
level строка все минимальная важность: debug, info, warn, error. Литерал all означает «без фильтра»
target строка точное совпадение цели трассировки, до 128 символов
q строка подстрока без учёта регистра по сообщению и цели, до 128 символов
after целое 0 вернуть только записи с seq больше указанного
limit целое 500 диапазон 1..1000
curl -sS -G "$BASE/api/v1/logs/plane" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode 'level=warn' \
  --data-urlencode 'limit=200'
{
  "head_seq": 918422,
  "capacity": 4096,
  "capture_level": "info",
  "capture_source": "plane.toml",
  "capture_environment_override": false,
  "entries": [
    { "seq": 918422, "ts_ms": 1755509102000, "level": "warn",
      "target": "cctv_plane::reconciler", "message": "node unreachable, retry scheduled" }
  ]
}

Записи идут от новых к старым: кольцо фильтруется, разворачивается и обрезается до limit.

Ошибки

Код Тело Причина
400 log_filter_too_long target или q длиннее 128 символов

Примечания. Поля capture_* описывают политику захвата — что вообще попадает в кольцо, — и это не то же самое, что фильтр отображения из параметров запроса. capture_environment_override: true означает, что уровнем владеет переменная окружения RUST_LOG и через API он не меняется.

GET /api/v1/logs/plane/stream#

Суперадмин

Назначение. Живой «хвост» того же кольца с тем же фильтром, применяемым на сервере.

Параметры запроса. Те же level, target, q (строят серверный фильтр) и last_event_id (запасная точка возобновления, заголовок Last-Event-ID важнее). Параметры after и limit здесь игнорируются.

curl -N -sS -G "$BASE/api/v1/logs/plane/stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept: text/event-stream' \
  --data-urlencode 'level=warn'
id: 918423
data: {"seq":918423,"ts_ms":1755509104000,"level":"warn","target":"cctv_plane::reconciler","message":"node unreachable, retry scheduled"}

:

Подходящие записи новее точки возобновления сначала повторяются от старых к новым, затем идёт живая выдача.

Ошибки: 400 log_filter_too_long; 429 live_stream_capacity.

Примечания. При захвате сообщение обрезается до 4096 байт, цель — до 128 байт, а значения, похожие на учётные данные, вычищаются.

Аудит действий#

Аудит — журнал того, кто что сделал: входы, изменения нод и хранилищ, выдача прав, экспорт архива, скачивание доказательств, правки настроек. Записи хранятся в PostgreSQL и переживают перезапуск.

Доступ здесь ролевой, а не «админский»: запрос может выполнить любая живая сессия, но роль user (оператор) получает 403 {"error":"audit is not visible to operators"}. Суперадмин видит все организации и может сузить выборку параметром ?org, администратор организации всегда ограничен своей. Чужая организация в ?org у не-суперадмина — 403 {"error":"cross_org"}.

GET /api/v1/audit#

Администратор — операторам аудит не виден.

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

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

Параметр Тип По умолчанию Описание
org uuid сужение для суперадмина
before_ts RFC 3339 курсор, часть 1
before_id uuid курсор, часть 2 — передаются только вместе
limit целое 100 диапазон 1..500
occurred_from RFC 3339 нижняя граница включительно
occurred_to RFC 3339 верхняя граница
actor_id uuid кто выполнил действие
action строка действие из закрытого словаря, например auth.login; полный список — GET /api/v1/audit/actions
object_type строка тип объекта, до 64 символов
object_id uuid конкретный объект
node_id uuid нода
q строка свободный поиск, 2..128 символов; ищет и внутри JSON-параметров записи

Свободный поиск (q) требует явного occurred_from, а окно (occurred_to или «сейчас») − occurred_from не должно превышать 31 день: иначе PostgreSQL не сможет отсечь месячные секции и опечатка превратится в чтение всей истории.

curl -sS -G "$BASE/api/v1/audit" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode 'occurred_from=2026-08-17T00:00:00Z' \
  --data-urlencode 'action=settings.update' \
  --data-urlencode 'limit=100'
{
  "rows": [
    {
      "id": "<UUID>",
      "occurred_at": "2026-08-17T18:22:41Z",
      "ended_at": null,
      "org_id": "<UUID>",
      "actor_id": "<UUID>",
      "actor_name": "admin",
      "actor_role": "superadmin",
      "ip": "203.0.113.10",
      "user_agent": "Mozilla/5.0",
      "action": "settings.update",
      "object_type": "plane",
      "object_id": null,
      "object_name": "plane",
      "params": { "section": "logging", "level": "info" },
      "node_id": null
    }
  ],
  "stream_cursor": "g:41927"
}

Ошибки

Код Тело Причина
400 bad_audit_cursor передана только одна половина курсора
400 bad_audit_range occurred_from больше occurred_to
400 audit_search_must_be_2_to_128_chars длина q
400 audit_search_requires_explicit_range q без occurred_from
400 audit_search_range_max_31_days окно поиска шире 31 дня
400 bad_audit_action действие вне словаря
400 bad_audit_object_type object_type длиннее 64 символов
403 cross_org чужая организация
403 audit is not visible to operators роль user

Примечания. Это не обычный конверт {"rows":…,"next":…}: поля next здесь нет. Следующую страницу соберите сами — возьмите occurred_at и id последней строки и передайте их как before_ts и before_id. Значение stream_cursor отдайте в /api/v1/audit/stream, чтобы получить живую ленту без пропусков.

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

Администратор — операторам аудит не виден.

Назначение. Одна запись целиком, вместе с JSON-параметрами params. Живой поток параметры не передаёт, поэтому карточку выбранной из потока строки достают именно так.

Параметры пути. id — uuid записи аудита.

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

Параметр Тип Обязательно Описание
occurred_at RFC 3339 да время записи: таблица аудита секционирована по времени, без него строку не найти
org uuid нет сужение для суперадмина
curl -sS -G "$BASE/api/v1/audit/<UUID>" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode 'occurred_at=2026-08-17T18:22:41Z'

Ошибки

Код Тело Причина
400 текст axum occurred_at отсутствует или не разбирается
403 cross_org, audit is not visible to operators организация, роль
404 not_found записи нет либо она вне вашей области видимости

GET /api/v1/audit/actions#

Администратор — операторам аудит не виден.

Назначение. Полный закрытый словарь действий — то, что допустимо в фильтре ?action, и то, из чего строится выпадающий список в интерфейсе.

Параметры. Отсутствуют.

curl -sS "$BASE/api/v1/audit/actions" \
  -H "Authorization: Bearer $TOKEN"
{ "rows": ["auth.login", "auth.login_failed", "auth.keys_rotated", "node.create",
           "storage.update", "grant.update", "export.dvr", "data.blob_download",
           "data.csv_export", "settings.update"] }

Словарь включает группы auth.*, node.*, storage.*, agent*.*, grant.*, view.*, export.dvr, data.*, org.*, user.*, tag.*, camera.*, mosaic.*, report.*, dashboard.*, widget.*, template.*, token.*, share.*, ptz.*, onvif.*, settings.update.

GET /api/v1/audit/export.csv#

Администратор — операторам аудит не виден.

Назначение. Выгрузка текущей выборки в CSV. Сама выгрузка тоже пишется в аудит как data.csv_export.

Параметры запроса. Те же, что у GET /api/v1/audit, кроме limit — он игнорируется: выгрузка всегда запрашивает 500 строк. Для следующей порции используйте before_ts / before_id.

curl -sS -G "$BASE/api/v1/audit/export.csv" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode 'occurred_from=2026-08-01T00:00:00Z' \
  -o audit.csv

Ответ — text/csv; charset=utf-8 с заголовком Content-Disposition: attachment; filename="audit.csv". Первая строка файла:

occurred_at,org_id,actor_id,actor_name,actor_role,ip,action,object_type,object_id,object_name,node_id,params

Ошибки: те же проверки, что у списка; 403 cross_org; 403 audit is not visible to operators.

Примечания.

  • Колонки user_agent в CSV намеренно нет, хотя в JSON-строке это поле есть.
  • Ячейки, начинающиеся с =, +, - или @ (в том числе после пробелов), предваряются апострофом, чтобы табличный процессор не выполнил подставленную формулу.

GET /api/v1/audit/stream#

Администратор — операторам аудит не виден.

Назначение. Живая лента аудита (SSE) с курсором, который переживает перезапуск контрол-плейна.

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

Параметр Тип По умолчанию Описание
org uuid сужение для суперадмина
last_event_id строка запасная точка возобновления; заголовок Last-Event-ID важнее

Формат курсора — g:<номер>, например g:41927: это глобальная монотонная последовательность PostgreSQL. Всё иное — отсутствие префикса, нечисловой хвост, номер больше текущего — трактуется как повод для пересинхронизации.

curl -N -sS "$BASE/api/v1/audit/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-18T09:50:11Z","ended_at":null,"org_id":"<UUID>","actor_id":"<UUID>","actor_name":"admin","actor_role":"superadmin","ip":"203.0.113.10","action":"camera.update","object_type":"camera","object_id":"<UUID>","object_name":"Въезд, столб 3","node_id":"<UUID>","updated":false}

:

Как читать поток:

  • id: — курсор записи, его же клиент вернёт при переподключении.
  • Поля params в кадре намеренно нет: уведомление PostgreSQL ограничено 8 КиБ. Параметры дочитывайте запросом GET /api/v1/audit/{id}?occurred_at=….
  • updated: true — это закрытие ранее начатой записи (одно из двух разрешённых продлений), а не новое действие: обновите существующую строку.
  • : delivery=replay помечает кадры, доигранные после переподключения; одинокое двоеточие раз в 15 секунд — пульс.
  • : resync-required — терминальный комментарий: поток завершается, потому что курсор испорчен, указывает в будущее, нужный номер вытеснен из кольца или подписчик отстал. Перечитайте GET /api/v1/audit и подпишитесь заново с новым stream_cursor.
  • Поток обрывается по истечении срока действия сессии: тело SSE не продлевает токен.

Ошибки: 403 cross_org; 403 audit is not visible to operators; 429 live_stream_capacity.

Настройки контрол-плейна#

Через API правится тот же plane.toml, что и вручную: сеть и TLS, уровень журналирования, хранилище доказательств и предел длительности экспорта архива. Запись атомарная и затрагивает только разрешённые ключи — посторонние строки и комментарии файла сохраняются.

Отдельного пространства /api/v1/config у контрол-плейна нет

Пути /api/v1/config, /api/v1/config/globals и /api/v1/config/logging не реализованы в этой версии: любой из них отвечает 501 {"error":"not_implemented"}. Конфигурация контрол-плейна правится через GET и PATCH /api/v1/settings/plane, конфигурация ноды — через GET /api/v1/nodes/{id}/config и PATCH /api/v1/nodes/{id}/config/globals, а её уровень журналирования — через GET и PATCH /api/v1/nodes/{id}/logging (см. Ноды и хранилища).

GET /api/v1/settings/plane#

Суперадмин

Назначение. Прочитать сетевые настройки, TLS, журналирование и хранилище доказательств в трёх разрезах: что использует запущенный процесс (effective), что записано в plane.toml (saved) и требуется ли перезапуск (pending_restart).

Параметры. Отсутствуют.

curl -sS "$BASE/api/v1/settings/plane" \
  -H "Authorization: Bearer $TOKEN"
{
  "network": {
    "effective": {
      "public_url": "https://cctv.example.com",
      "listen": "0.0.0.0:443",
      "tls": { "mode": "acme", "cert": null, "key": null,
               "hosts": ["cctv.example.com"], "store_dir": "/var/lib/hastreamer-plane/tls",
               "acme_contact": "", "acme_listen": "0.0.0.0:80" }
    },
    "saved": { "public_url": "https://cctv.example.com", "listen": "0.0.0.0:443",
               "tls": { "mode": "acme" } },
    "pending_restart": false,
    "certificate": {
      "mode": "acme",
      "configured_hosts": ["cctv.example.com"],
      "automatic_renewal": true,
      "renewal_window_days": 30,
      "temporary_self_signed": false,
      "status_hint": null,
      "certificate": {
        "fingerprint": "…", "subject": "cctv.example.com", "issuer": "…",
        "serial": "…", "hosts": ["cctv.example.com"],
        "not_before": "2026-07-01T00:00:00Z", "not_after": "2026-09-29T00:00:00Z",
        "days_left": 42, "renew_after": "2026-08-30T00:00:00Z"
      }
    }
  },
  "logging": { "minimum_level": "info", "effective_minimum_level": "info",
               "source": "plane.toml", "environment_override": false, "applies_live": true },
  "evidence_storage": {
    "effective": { "backend": "local", "dir": "/var/lib/hastreamer-plane/evidence",
                   "max_bytes": 536870912000, "volume_retention": "disabled",
                   "low_watermark_percent": 70, "high_watermark_percent": 85,
                   "critical_watermark_percent": 95 },
    "saved": { "backend": "local", "dir": "/var/lib/hastreamer-plane/evidence" },
    "pending_restart": false,
    "rotation_blocked": false,
    "backend_divergence": null,
    "stats": { "state": "ok", "logical_bytes": 41203998720, "physical_bytes": 41310000000,
               "event_count": 1284551, "attachment_count": 962103,
               "capacity_bytes": 1000204886016, "available_bytes": 512000000000,
               "used_percent": 49,
               "operations": { "in_flight": 0, "total_ops": 88213, "errors": 0,
                               "ops_per_second": 12.4, "bytes_per_second": 1048576,
                               "average_latency_ms": 3.1, "max_latency_ms": 42.0 } }
  },
  "source": "plane.toml",
  "dvr_export_max_secs": 3600
}

Режимы TLS (tls.mode): off, external (TLS терминирует внешний прокси), pem (свой сертификат), self_signed, acme. Подробности режимов — на странице TLS и сертификаты.

Хранилище доказательств бывает локальным (backend: "local", ключ dir) и объектным (backend: "s3", ключи endpoint, bucket, region, https, addressing). Секреты никогда не возвращаются — вместо них приходят два признака access_key_configured и secret_key_configured.

Ошибки

Код Тело Причина
500 plane_config_unreadable:<подробность> plane.toml не читается или не разбирается
503 evidence_storage_unavailable подсистема хранилища доказательств недоступна

Примечания. pending_restart: true означает, что сохранённое значение отличается от того, с которым процесс стартовал: изменение вступит в силу только после перезапуска. applies_live: false в блоке журналирования означает, что запущенный процесс работает не с сохранённым уровнем — либо plane.toml правили напрямую, либо уровнем владеет RUST_LOG.

PATCH /api/v1/settings/plane#

Суперадмин

Назначение. Изменить настройки контрол-плейна. Раздел, который вы не передали, не трогается.

Тело запроса

Поле Тип Описание
dvr_export_max_secs целое | null предел длительности одного экспорта архива, 60..86400 секунд
network объект | null public_url, listen и полный объект tlsвсе три обязательны вместе
log_level строка | null debug, info, warn, error
evidence_storage объект | null конфигурация хранилища доказательств, вид выбирается ключом backend
acknowledge_destructive_volume_retention bool одноразовое подтверждение опасной смены политики; по умолчанию false, не сохраняется

Для evidence_storage с "backend":"local" обязательны dir (абсолютный путь), volume_retention (disabled либо oldest_purge_day) и все три порога low_watermark_percent, high_watermark_percent, critical_watermark_percent; max_bytes необязателен.

Для "backend":"s3" обязательны endpoint (в форме хост[:порт], без схемы), bucket, addressing (path либо virtual_host), max_bytes (у объектного хранилища нет переносимого способа узнать свободное место), volume_retention и три порога; region по умолчанию us-east-1, httpstrue. Ключи access_key и secret_key передаются только парой; опустите оба, чтобы сохранить уже записанные.

Проверки: пороги обязаны удовлетворять 1 ≤ low < high < critical ≤ 99; max_bytes — не меньше 64 МиБ; listen — корректный адрес «IP:порт»; при tls.mode: "off" публичный URL должен быть http://, во всех прочих режимах — https://; режим pem требует cert и key; self_signed и acme требуют список hosts, включающий имя из публичного URL; acme дополнительно требует доменных имён (не IP-литералов) и acme_listen, отличного от listen.

# Сменить уровень журналирования — единственная настройка, применяемая на лету.
curl -sS -X PATCH "$BASE/api/v1/settings/plane" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"log_level":"debug"}'
{
  "dvr_export_max_secs": null,
  "network": null,
  "logging": { "minimum_level": "debug", "effective_minimum_level": "debug",
               "source": "plane.toml", "environment_override": false, "applies_live": true },
  "evidence_storage": null
}

Раздел, которого вы не касались, возвращается как null. Правка «ничего не меняющая» отвечает 200, но не пишет файл и не создаёт запись аудита.

Ошибки

Код Тело Причина
400 no_settings_supplied не передан ни один изменяемый раздел
400 dvr_export_max_secs_out_of_range значение вне 60..86400
400 invalid_public_url публичный URL не разбирается
400 destructive_volume_retention_acknowledgement_required переход на oldest_purge_day без подтверждения в том же запросе
400 текст проверки конфигурации человекочитаемое сообщение о неверном значении
409 settings_busy другая правка держала блокировку дольше 10 секунд
409 rust_log_override уровнем журналирования владеет RUST_LOG, через API он не меняется
409 settings_changed plane.toml изменился на диске после загрузки формы
409 event_storage_rotation_blocked в выводимом из эксплуатации хранилище доказательств ещё лежат объекты
500 plane_config_unreadable:<подробность> файл не читается
502 подробность предпроверки кандидат-хранилище не прошёл проверку записи или связности
503 evidence_storage_unavailable, audit_unavailable подсистемы недоступны
Смена сети, TLS и хранилища доказательств требует перезапуска

Изменения network и evidence_storage не применяются на лету — процесс продолжает работать со старыми значениями до перезапуска, о чём говорит pending_restart: true. Ошибётесь в listen, public_url или режиме TLS — контрол-плейн не поднимется после рестарта, и чинить придётся с консоли сервера. Меняйте эти разделы только имея доступ к машине.

Примечания. Уровень журналирования применяется на лету, и если живое применение не удалось, запись в файл откатывается. Запись сериализована на весь процесс и защищена сверкой содержимого, поэтому параллельная правка другим администратором будет отклонена, а не молча затёрта.

POST /api/v1/settings/plane/evidence-storage/test#

Суперадмин

Назначение. Предпроверка конфигурации хранилища доказательств без сохранения: доступна ли запись и связность и можно ли вообще применить такой переезд прямо сейчас.

Тело запроса

Поле Тип Обязательно Описание
evidence_storage объект да тот же объект, что и в PATCH
curl -sS -X POST "$BASE/api/v1/settings/plane/evidence-storage/test" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "evidence_storage": {
          "backend": "local",
          "dir": "/srv/evidence",
          "volume_retention": "disabled",
          "low_watermark_percent": 70,
          "high_watermark_percent": 85,
          "critical_watermark_percent": 95
        }
      }'
{ "ok": true, "rotation_blocked": false }

Ошибки

Код Тело Причина
400 текст проверки конфигурации значения не проходят проверку
500 plane_config_unreadable:<подробность> текущий файл не читается
502 подробность отказа кандидат-хранилище не подтвердило работоспособность

Примечания. rotation_blocked: true означает «конфигурация корректна, но сохранить её прямо сейчас нельзя»: в выводимом хранилище ещё есть объекты. Это честная предпроверка — она не даёт интерфейсу отрапортовать об успехе и провалить сохранение.

Служебные эндпоинты#

GET /api/v1/version#

Любой вошедший — намеренно не публичный.

Назначение. Точные сведения о сборке запущенного процесса: то, что показано в подвале интерфейса. Обращения к базе нет.

Параметры. Отсутствуют.

curl -sS "$BASE/api/v1/version" \
  -H "Authorization: Bearer $TOKEN"
{
  "version": "2026.8.0",
  "git": "0036cc0e",
  "git_sha": "0036cc0e00000000000000000000000000000000",
  "dirty": false,
  "profile": "release",
  "opt_level": "3",
  "debug": false,
  "target": "x86_64-unknown-linux-gnu",
  "features": ["cctv-plane"]
}

Примечания. Строка сборки не выставлена на неавторизованную поверхность сознательно: для пробы живости используйте публичный GET /health.

GET /api/v1/usage#

Администратор — администратор организации читает только свою организацию.

Назначение. Мгновенный срез потребления квот организацией: то, что показывает страница использования и баннеры при заполнении квоты на 80 % и выше. Читаются готовые счётчики, а не COUNT по таблицам.

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

Параметр Тип По умолчанию Описание
org_id uuid своя организация суперадмин может указать любую; администратор организации — только свою, иначе 403 forbidden
curl -sS "$BASE/api/v1/usage?org_id=<UUID>" \
  -H "Authorization: Bearer $TOKEN"
{
  "org_id": "<UUID>", "org_name": "Организация 1",
  "cameras_count": 148, "max_cameras": 200,
  "users_count": 12, "max_users": 50,
  "attachment_bytes": 41203998720, "attachment_quota_bytes": 107374182400,
  "sessions_count": 9, "max_sessions_per_user": 5,
  "dashboards_count": 3, "max_dashboards": 20,
  "reports_count": 11, "max_reports": 100,
  "tags_count": 24, "max_tags": 1024,
  "event_templates_count": 6, "max_event_templates": 100,
  "agents_count": 2, "max_agents": 10,
  "agent_devices_count": 31, "max_agent_devices": 200,
  "agent_access_rules_count": 4, "max_agent_access_rules": 50,
  "events_count": 1284551, "attachments_count": 962103
}

Ошибки

Код Тело Причина
403 forbidden администратор организации запросил чужую организацию
404 not_found организации не существует

Примечания. sessions_count — живой счёт неотозванных и неистёкших сессий по всем пользователям организации. Пустое (null) значение предела всегда означает «без ограничения», а не «ноль».

GET /api/v1/sessions#

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

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

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

Параметр Тип По умолчанию Описание
camera_id uuid только одна камера; чужая камера — 404 not_found (существование не раскрывается)
q строка поиск без учёта регистра, до 128 символов; сопоставляется с stream, protocol, client_ip, user_agent, sub, node_name, camera_name, subject_name
limit целое 50 диапазон 1..100
cursor строка непрозрачный курсор из предыдущего ответа
curl -sS "$BASE/api/v1/sessions?limit=50" \
  -H "Authorization: Bearer $TOKEN"
{
  "items": [
    { "stream": "cam/<UUID>/main", "protocol": "mse-ws", "client_ip": "203.0.113.25",
      "user_agent": "Mozilla/5.0", "sub": "<UUID>", "connected_ms": 48213,
      "node_id": "<UUID>", "node_name": "node-1",
      "camera_id": "<UUID>", "camera_name": "Въезд, столб 3",
      "subject_kind": "user", "subject_id": "<UUID>", "subject_name": "operator1" }
  ],
  "limit": 50,
  "next_cursor": "<непрозрачная строка>",
  "partial_nodes": [],
  "sampled_at": 1755509102
}

Ошибки

Код Тело Причина
400 query_too_long q длиннее 128 символов
404 not_found камера неизвестна либо принадлежит чужой организации
500 url_build_failed внутренняя ошибка сборки адреса ноды

Примечания. Страница — не снимок всего флота: один запрос обращается максимум к 4 нодам с тайм-аутом 2 секунды на каждую. Нода, не ответившая вовремя, пропускается и перечисляется в partial_nodes, а обход продолжается. Поэтому страница бывает короче limit и всё равно имеет next_cursor — листайте до next_cursor: null, а непустой partial_nodes считайте признаком неполноты, а не ошибкой. Список зрителей одной ноды доступен отдельно: GET /api/v1/nodes/{id}/sessions, см. Ноды и хранилища.

POST /api/v1/admin/keys/rotate#

Суперадмин

Назначение. Ротация ключа, которым подписываются сессии. Два режима: смена эпохи идентификатора ключа (аварийный рычаг «сделать все выданные сессии недействительными») и полная замена ключевого материала при подозрении на компрометацию.

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

Параметр Тип По умолчанию Описание
scope строка смена эпохи единственное распознаваемое значение — material (полная замена ключа); любое другое, как и отсутствие параметра, означает смену эпохи

Тело запроса. Отсутствует.

curl -sS -X POST "$BASE/api/v1/admin/keys/rotate" \
  -H "Authorization: Bearer $TOKEN"
{ "kid": "<новый идентификатор ключа>", "scope": "kid" }

Ошибки

Код Тело Причина
500 rotate_failed ротация не выполнена
Ротация ключа выкидывает из системы всех, включая вас

Все выданные JWT перестают проверяться немедленно — все пользователи разлогинены, включая инициатора. Открытые потоки SSE и активные сессии просмотра оборвутся. Управляющие токены нод это не затрагивает: выдача сессий и управление нодами подписываются разными ключами. Действие пишется в аудит как auth.keys_rotated.

Модульные токены#

Модульный токен — bearer-секрет, которым внешний аналитический модуль отправляет события запросом POST /api/v1/events. Это отдельное от сессий пространство учётных данных. Секрет хранится только в виде хеша SHA-256: ни список, ни чтение никогда не возвращают секрет.

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

GET /api/v1/module-tokens#

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

Назначение. Список модульных токенов (только метаданные).

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

Параметр Тип По умолчанию Описание
org uuid своя организация сужение для суперадмина
after uuid курсор: next из предыдущей страницы
limit целое 500 диапазон 1..500
q строка подстрока имени без учёта регистра, до 128 символов
curl -sS "$BASE/api/v1/module-tokens?limit=100" \
  -H "Authorization: Bearer $TOKEN"
{
  "rows": [
    { "id": "<UUID>", "org_id": "<UUID>", "name": "Модуль распознавания",
      "rate_limit": 50, "enabled": true,
      "created_at": "2026-08-01T10:00:00Z", "last_used_at": "2026-08-18T09:44:10Z" }
  ],
  "next": null
}

Значение rate_limit: null означает предел по умолчанию, заданный при установке.

Ошибки

Код Тело Причина
400 query_too_long q длиннее 128 символов
403 cross_org чужая организация

POST /api/v1/module-tokens#

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

Назначение. Выпустить модульный токен. Открытый секрет возвращается ровно один раз — второй раз получить его нельзя, только выпустить новый ротацией.

Тело запроса

Поле Тип Обязательно По умолчанию Описание
name строка да 1..128 символов; не может начинаться с __
rate_limit целое | null нет null предел приёма событий в секунду; значение не меньше 1, null — предел по умолчанию
org_id uuid | null нет своя организация суперадмин может указать любую
curl -sS -X POST "$BASE/api/v1/module-tokens" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Модуль распознавания","rate_limit":50}'
{
  "id": "<UUID>", "org_id": "<UUID>", "name": "Модуль распознавания",
  "rate_limit": 50, "enabled": true,
  "created_at": "2026-08-18T09:55:00Z", "last_used_at": null,
  "secret": "<секрет, показывается один раз>"
}

Ответ отдаётся с Cache-Control: no-store и Pragma: no-cache.

Ошибки

Код Тело Причина
400 bad_name имя пустое или длиннее 128 символов
400 reserved_name имя начинается с __
400 bad_rate_limit предел меньше 1
403 cross_org чужая организация
409 already_exists имя занято

PATCH /api/v1/module-tokens/{id}#

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

Назначение. Переименовать токен, изменить его предел приёма или включить/выключить его. Выключенный токен перестаёт принимать события немедленно. Секрет при этом не меняется.

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

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

Поле Тип Описание
name строка новое имя, проверки как при создании
rate_limit целое | null различает пропуск и null: поле опущено — без изменений; явный null — вернуть предел по умолчанию; число — задать свой
enabled bool включить или выключить приём
curl -sS -X PATCH "$BASE/api/v1/module-tokens/<UUID>" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"enabled":false}'

Ошибки: 400 bad_name, reserved_name, bad_rate_limit; 403 cross_org; 404 not_found; 409 already_exists.

POST /api/v1/module-tokens/{id}/rotate#

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

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

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

curl -sS -X POST "$BASE/api/v1/module-tokens/<UUID>/rotate" \
  -H "Authorization: Bearer $TOKEN"

Ответ — строка токена плюс новый secret, с заголовками Cache-Control: no-store и Pragma: no-cache.

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

Примечания. Ротация — жёсткое переключение без периода совместимости: обновите настройки модуля-производителя в том же окне обслуживания.

DELETE /api/v1/module-tokens/{id}#

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

Назначение. Удалить модульный токен.

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

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

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

Примечания. Уже принятые события сохраняются, но интервальное событие, которое умел закрывать только этот токен, автоматически закрыть больше некому — такие эпизоды придётся закрывать администратору организации запросом PATCH /api/v1/events/{id}, см. События и аналитика.

Не реализовано в этой версии#

Маршрутизатор контрол-плейна — единственный источник истины о наличии эндпоинта. Перечисленные ниже пути не существуют: любой из них отвечает

{ "error": "not_implemented" }

со статусом 501. Считайте 501 однозначным «такого эндпоинта нет» — сопровождающий текст в теле ответа является исторической служебной пометкой и не является обещанием реализовать путь.

Запрошенный путь Что использовать вместо него
/api/v1/config, /api/v1/config/globals, /api/v1/config/logging конфигурация контрол-плейна — GET и PATCH /api/v1/settings/plane; конфигурация ноды — GET /api/v1/nodes/{id}/config и PATCH /api/v1/nodes/{id}/config/globals; уровень журналирования ноды — GET и PATCH /api/v1/nodes/{id}/logging
/api/v1/license у контрол-плейна собственного эндпоинта лицензии нет. Лицензия нодыGET /api/v1/nodes/{id}/license; сводка по флоту — счётчики nodes_unlicensed и nodes_grace в GET /api/v1/monitor/fleet/summary
/api/v1/logs (без уточнения) кольцо контрол-плейна — /api/v1/logs/plane и /api/v1/logs/plane/stream; кольцо ноды — /api/v1/nodes/{id}/logs

Отдельный случай: GET /api/v1/nodes/{id} отвечает 405 Method Not Allowed, потому что на этом пути зарегистрирован только PATCH. Одну ноду читайте из списка GET /api/v1/nodes. Ответ 405 означает «путь есть, метод другой», а 501 — «пути нет»; при разведке API это полезно различать.

Что опрашивать для мониторинга#

Внешняя система мониторинга работает с контрол-плейном по описанным здесь JSON-эндпоинтам: экспозиции Prometheus у него нет. Метрики в формате Prometheus отдаёт стриминговая нода — её и снимайте штатным сборщиком метрик, а состояние контрол-плейна собирайте так:

Что опрашивать Периодичность На что заводить оповещение
GET /health (без авторизации) 10–15 с процесс не отвечает — самая дешёвая проба живости
GET /api/v1/monitor/fleet/summary 30 с nodes_reachable < nodes_total; streams_failing > 0; рост cameras_unavailable и cameras_failing; nodes_unlicensed > 0 или nodes_grace > 0; stream_samples_stale > 0 дольше нескольких обходов
GET /api/v1/monitor/plane 60 с устойчивый рост rss_mb; proc_cpu_cores заметно выше обычного; db_pool_in_use близко к db_pool_max; рост open_fds
GET /api/v1/settings/plane 5–15 мин evidence_storage.stats.used_percent выше порога high_watermark_percent; ненулевые failed_jobs и pressure_dropped_*; certificate.days_left меньше 14; неожиданный pending_restart: true
GET /api/v1/usage?org_id=<UUID> 15 мин на организацию любое *_count выше 80 % своего max_*; attachment_bytes близко к attachment_quota_bytes
GET /api/v1/logs/plane?level=error&after=<последний seq> 60 с появление новых записей уровня error; запоминайте head_seq и передавайте его в after
GET /api/v1/audit?occurred_from=…&action=auth.login_failed 5 мин всплеск неудачных входов; отдельно отслеживайте auth.keys_rotated, settings.update, node.create, export.dvr
GET /api/v1/version при выкладке версия отличается от ожидаемой — процесс не перезапустился или откатился

Практические замечания:

  • Где возможно, подписывайтесь, а не опрашивайте. У сводки флота, телеметрии нод, метрик процесса, журнала и аудита есть SSE-потоки; они дешевле цикла опроса и дают изменения сразу. Помните про лимит живых потоков: 1024 на процесс и 32 на одного пользователя.
  • Заведите отдельную учётную запись для мониторинга. Большинство служебных эндпоинтов требуют роли суперадмина; отдельная учётная запись отделяет автоматические обращения от действий людей в аудите. Токен сессии не вечен — обновляйте его при 401.
  • Не опрашивайте телеметрию чаще, чем она обновляется. Метрики процесса снимаются раз в 2 секунды, сводка флота — раз в обход; более частый опрос не даст новых данных.
  • Ежедневный операционный чек-лист и работа с метриками ноды описаны на странице Мониторинг и метрики.