Мониторинг и служебное
Состояние флота, аудит действий, настройки контрол-плейна, версия и потребление ресурсов.
Раздел описывает служебную часть 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"}. В таблицах ниже перечислены только ошибки, специфичные для
запроса.
У контрол-плейна нет эндпоинта /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, https — true. Ключи 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 |
подсистемы недоступны |
Изменения 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 секунды, сводка флота — раз в обход; более частый опрос не даст новых данных.
- Ежедневный операционный чек-лист и работа с метриками ноды описаны на странице Мониторинг и метрики.