SIRINVIDEO Документация администратора и интегратора На сайт 2026.08

Конфигурация и система

Чтение и запись hastreamer.conf через API, проверка и откат, сертификаты, хранилища, ресурсы хоста, лицензия, журналы и аудит.

Конфигурация сервера — единственный источник истины: панель, API и файл hastreamer.conf правят одно и то же. API даёт три способа изменить её, от самого грубого к самому точному:

Способ Маршрут Когда применять
Записать файл целиком PUT /config развёртывание из системы управления конфигурацией
Изменить глобальные директивы PATCH /config/globals сменить порт, уровень журнала, переписать блок auth
Изменить один поток PATCH /streams/{id} всё, что касается одного потока — см. Потоки

Общие правила (BASE, TOKEN, форма ошибок, бейджи прав) — на странице Введение и аутентификация.

Версии, хеш и защита от гонки#

Каждая записанная конфигурация получает номер версии epoch и хеш содержимого hash. Значение epoch никогда не уменьшается: даже откат добавляет новую версию.

Хеш — это токен сравнения-и-замены. Передавайте его в поле base.hash при записи: если конфигурацию изменил кто-то другой между вашим чтением и записью, сервер ответит 409 с актуальными epoch и hash, а не затрёт чужую правку молча.

GET /config → hash=4a186afe…
                                  ← кто-то другой применил свою правку, hash стал 9f2c11a7…
PUT /config {text, base:{hash:"4a186afe…"}} → 409 config conflict, epoch=13, hash=9f2c11a7…

Правильная реакция на 409 — перечитать конфигурацию, наложить свою правку заново и повторить запрос.

Конфигурация#

GET /api/v1/config#

Запись — только сессия администратора или api_write_token.

Возвращает файл конфигурации дословно, его разобранную модель, текущие epoch и hash, сведения о сборке и до 32 последних звеньев журнала версий.

Токену чтения этот маршрут запрещён — 403

Ответ содержит секреты в открытом виде: URL источников с паролями камер, ключи S3, auth.admin_password, оба статических токена. Поэтому api_read_token получает здесь 403 {"error":"forbidden","reason":"read_token_cannot_read_config"} — иначе токен уровня чтения одним запросом вышел бы за границы своего уровня.

Внешней панели, которой нужен только порог журналирования и хеш, отдавайте узкую проекцию GET /config/logging.

curl -sS "$BASE/api/v1/config" -H "Authorization: Bearer $TOKEN"
{"epoch":10,"hash":"4a186afe976f6ef3","shared_hash":"85851d2f59d9e592",
 "version":"2026.8.0",
 "build":{"profile":"release","target":"x86_64-unknown-linux-gnu","features":["full"]},
 "text":"http 8080;\nhttps 443;\n\nauth {\n  admin_password \"…\";\n}\n\nstream cam1 {\n  input rtsp://…;\n  dvr local 6h 20G;\n}\n",
 "parsed":{"http_port":8080,"https_port":443,"cores":4,"log_level":"info","max_sessions":0},
 "chain":[{"epoch":10,"hash":"4a186afe976f6ef3","shared_hash":"85851d2f59d9e592"}],
 "locked":false}

Ошибки

Код Тело Когда
403 {"error":"forbidden","reason":"read_token_cannot_read_config"} предъявлен токен чтения
501 {"title":"config mutation unavailable (no config file)"} сервер запущен без файла конфигурации

GET /api/v1/config/logging#

Чтение

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

curl -sS "$BASE/api/v1/config/logging" -H "Authorization: Bearer $TOKEN"
{"configured_level":"info","hash":"4a186afe976f6ef3","locked":false}

GET /api/v1/config/streams#

Чтение

Голый массив — не конверт items. Все потоки из конфигурации, включая выключенные, которых нет в рантайм-списке GET /streams. Именно отсюда берётся ответ на вопрос «какие потоки настроены, но не работают».

curl -sS "$BASE/api/v1/config/streams" -H "Authorization: Bearer $TOKEN"
[{"path":"cam1","disabled":false,"record":true,"labels":[],"retention":"6h · 20G"},
 {"path":"live/cam2","disabled":true,"record":false,"labels":["lobby"],"retention":""}]

GET /api/v1/config/history#

Чтение

Только дополняемый журнал версий, голый массив, от старых записей к новым.

curl -sS "$BASE/api/v1/config/history" -H "Authorization: Bearer $TOKEN"
[{"epoch":1,"hash":"d9b4d2d2a819ff24","shared_hash":"85851d2f59d9e592",
  "ts_ms":1786787600937,"actor":"system","via":"boot"},
 {"epoch":2,"hash":"7c3e15b09ad2f846","shared_hash":"85851d2f59d9e592",
  "ts_ms":1786787630567,"actor":"admin","via":"api"}]

Поле via называет источник правки: boot (разбор файла при старте), api, cert-acme, cert-delete и так далее. Поле actorsystem или имя исполнителя.


GET /api/v1/config/schema#

Чтение

Статический реестр редактируемых глобальных настроек: имя поля, соответствующая директива, тип, значение по умолчанию, класс применения и подсказка. Из него панель строит форму конфигурации. Чистое чтение, не зависящее от состояния узла.

curl -sS "$BASE/api/v1/config/schema" -H "Authorization: Bearer $TOKEN"
{"fields":[{"field":"http_port","directive":"http","ty":"port","default":8080,
            "blast":"restart","group":"listeners","doc":"HLS / API / player HTTP port."},
           {"field":"log_level","directive":"log_level","ty":"enum","default":"info",
            "blast":"hot","group":"tuning","doc":"Minimum severity captured."}]}

Поле blast — тот самый класс применения: hot действует сразу, restart требует перезапуска службы. См. Применение изменений.


POST /api/v1/config/validate#

Запись

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

Тело запроса

Поле Тип Обязательно Смысл
text строка да полный текст конфигурации-кандидата
curl -sS -X POST "$BASE/api/v1/config/validate" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"text":"http 8080;\n\nstream cam1 {\n  input rtsp://10.0.0.10:554/stream1;\n}\n"}'
{"ok":true,
 "classification":{"respawn":["cam1"],"added":["live/cam2"],"removed":[],
                   "restart_required":["http_port 8080 → 8090"],"untouched":12}}
Неудачная проверка тоже отвечает HTTP 200

Смотрите на поле ok, а не на код ответа. Ошибочный кандидат выглядит так:

{"ok":false,"errors":"unknown directive \"sourse\" in stream \"cam1\""}

PUT /api/v1/config#

Запись

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

Тело запроса

Поле Тип Обязательно Смысл
text строка да полный текст новой конфигурации
base.hash строка нет, но настоятельно рекомендуется хеш из последнего GET /config
curl -sS -X PUT "$BASE/api/v1/config" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"text":"http 8080;\n\nstream cam1 {\n  input rtsp://10.0.0.10:554/stream1;\n  dvr local 6h 20G;\n}\n",
       "base":{"hash":"4a186afe976f6ef3"}}'
{"epoch":11,"hash":"9f2c11a70bd4e650","changed":true}

Ошибки

Код Тело Когда
400 {"title":"bad request","detail":"need \"text\""} в теле нет ключа text
403 {"error":"forbidden","reason":"read_token_cannot_write"} предъявлен токен чтения
409 {"title":"config conflict","detail":"the config changed under you — rebase and retry","epoch":13,"hash":"9f2c11a7…"} base.hash не совпал с текущим
413 тело больше 1 МиБ
422 {"title":"invalid config","detail":"…"} текст не разбирается
423 {"title":"config locked"} существует файл <config>.locked
501 {"title":"config mutation unavailable (no config file)"} сервер запущен без файла конфигурации
PUT /config заменяет файл целиком

Всё, чего нет в переданном text, будет удалено: потоки, хранилища, блок auth. Безопасная последовательность — GET /config → правка поля textPOST /config/validatePUT /config с тем же base.hash.


PATCH /api/v1/config/globals#

Запись

Структурная правка верхнего уровня конфигурации — всего, кроме блоков stream. Операции применяются строго в порядке setblocksunset, после чего результат проходит тот же путь, что и запись целиком: сравнение-и-замена, журнал версий, классификация, аудит.

Тело запроса

Поле Тип Смысл
base.hash строка хеш из последнего GET /config (защита от гонки)
set объект директива → значение задать или изменить одиночные директивы
blocks объект имя блока → тело переписать блок целиком (auth, vod, cluster, …)
unset массив имён директив удалить директивы
# Сменить порог журналирования и убрать RTMP-слушатель
curl -sS -X PATCH "$BASE/api/v1/config/globals" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"base":{"hash":"4a186afe976f6ef3"},
       "set":{"log_level":"debug"},
       "unset":["rtmp"]}'
{"epoch":12,"hash":"a41c9b2ee5f70d18","changed":true}
# Переписать блок auth целиком — так меняется пароль администратора
curl -sS -X PATCH "$BASE/api/v1/config/globals" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"base":{"hash":"a41c9b2ee5f70d18"},
       "blocks":{"auth":"admin_password \"новый-пароль\";\nadmin_ip_bind on;"}}'
Блок передаётся целиком

blocks заменяет тело блока полностью. Если в примере выше не указать api_read_token, он будет удалён из конфигурации. Прочитайте текущий блок в GET /configtext прежде, чем переписывать.

Ошибки

Код Когда
400 тело не является объектом JSON
403 предъявлен токен чтения
409 base.hash не совпал с текущим
413 тело больше 1 МиБ
422 правку невозможно наложить (bad edit) либо результат не разбирается (invalid config)
423 конфигурация заморожена
501 сервер запущен без файла конфигурации

POST /api/v1/config/rollback#

Запись

Применяет байты указанной версии заново — как новую версию. Журнал только дополняется.

Тело запроса

Поле Тип Обязательно Смысл
epoch целое да номер версии из GET /config/history
curl -sS -X POST "$BASE/api/v1/config/rollback" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"epoch":2}'
{"epoch":13,"hash":"7c3e15b09ad2f846","changed":true,"rolled_from":2}

Обратите внимание: epoch в ответе — новый (13), а hash совпадает с версией 2. Откат не уменьшает номер версии, а добавляет её копию в конец журнала, поэтому откат отката тоже возможен.

Ошибки

Код Тело Когда
400 {"title":"bad request"} в теле нет ключа epoch
403 предъявлен токен чтения
404 {"title":"version not found","detail":"epoch 99: not in the journal"} такой версии в журнале нет
422 байты версии больше не разбираются
423 конфигурация заморожена
501 сервер запущен без файла конфигурации

Сертификаты#

Записи хранилища SNI плюс сертификат по умолчанию. Данные узло-локальные: поля читаются из сохранённого файла, а не из конфигурации, поэтому показывают то, что сервер реально предъявит.

GET /api/v1/certs#

Чтение

curl -sS "$BASE/api/v1/certs" -H "Authorization: Bearer $TOKEN"
{"certificates":[
   {"name":"media.example.com","mode":"acme","hosts":["media.example.com"],
    "fingerprint":"5b7e2a09c4d16f83…",
    "certificate":{"subject":"CN=media.example.com","issuer":"…",
                   "not_before":1786792102,"not_after":1794568101,
                   "days_left":85,"renew_after":1791976101},
    "acme":{"status":"ok","issued":1786795603,"checked":1786795603,
            "days_left":85,"fails":0,"error":"","scan_interval_secs":60}}],
 "default":{"cert":"","certificate":{"subject":"CN=localhost","days_left":755821}}}

POST /api/v1/certs/{name}#

Запись

Две взаимоисключающие формы тела: загрузка готового PEM либо заказ автовыпуска ACME.

Параметры пути

Параметр Тип и допустимые значения Смысл
name [A-Za-z0-9._-]+, без . и .., литеральный / отвергается имя записи в хранилище SNI

Тело запроса — загрузка PEM

Поле Тип Обязательно Смысл
hosts массив строк да имена, для которых предъявляется сертификат
cert строка PEM да лист
key строка PEM да закрытый ключ
ca строка PEM нет промежуточные сертификаты
curl -sS -X POST "$BASE/api/v1/certs/media.example.com" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d "$(python3 - <<'PY'
import json
print(json.dumps({
  "hosts": ["media.example.com"],
  "cert": open("/etc/ssl/media.example.com/fullchain.pem").read(),
  "key":  open("/etc/ssl/media.example.com/privkey.pem").read(),
}))
PY
)"

Тело запроса — автовыпуск ACME

Поле Тип Обязательно Смысл
acme true да режим автовыпуска
hosts массив строк да только конкретные имена
curl -sS -X POST "$BASE/api/v1/certs/media.example.com" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"acme":true,"hosts":["media.example.com"]}'
{"name":"media.example.com","hosts":["media.example.com"],"mode":"acme","changed":true}

Примечания. Пара «лист + ключ» проверяется на соответствие до того, как что-либо будет сохранено; файлы кладутся с правами 0600 через временный файл и атомарное переименование, после чего TLS подхватывает их без перезапуска. Ответ на заказ ACME подтверждает только регистрацию: сам выпуск асинхронный, следите за полем acme.status в списке.

Ошибки

Код Тело Когда
403 предъявлен токен чтения
413 тело больше 1 МиБ
422 {"title":"invalid certificate","detail":"ACME/HTTP-01 cannot issue the wildcard \"*.example.com\" — give a concrete hostname"} неверное имя, нет ни одного хоста, нет cert/key, лист и ключ не пара, для ACME запрошено подстановочное имя
423 конфигурация заморожена

DELETE /api/v1/certs/{name}#

Запись

Убирает запись из хранилища SNI и обновляет TLS без перезапуска.

curl -sS -X DELETE "$BASE/api/v1/certs/media.example.com" \
  -H "Authorization: Bearer $TOKEN"
{"name":"media.example.com","deleted":true,"changed":true}

Хранилища#

Хранилище — том для записи архива: локальный каталог или ярус S3. Управление хранилищами всегда узло-локально. Подробнее о смысле параметров — Хранилища и S3.

GET /api/v1/storages#

Чтение

Голый массив состояния всех хранилищ узла.

curl -sS "$BASE/api/v1/storages" -H "Authorization: Bearer $TOKEN"
[{"name":"local","path":"/var/lib/hastreamer/dvr",
  "identity":"","identity_healthy":true,"maintenance_healthy":true,
  "total_bytes":84444717056,"used_bytes":31312314368,"avail_bytes":53132402688,
  "evict_at_percent":90,"reclaimed_files":0,"reclaimed_bytes":0,
  "streams":[["cam1",21,21012998324],["cam2",8,1843442769]],
  "read_iops":0,"write_iops":5,"read_bps":0,"write_bps":1497217,
  "used_growth_bps":-380330,"fill_eta_secs":null,
  "s3_healthy":true,"s3_errors":0,"cold_bytes":0,"tiered_bytes":0}]

Элементы массива streams — тройки [идентификатор потока, число сегментов, занято байт].

Снимок обновляется не посекундно

Тело публикуется проходом обслуживания архива раз в dvr_cleanup_secs (по умолчанию 600 с); поверх него в момент запроса накладываются живые счётчики S3. Не опрашивайте этот маршрут чаще, чем раз в несколько секунд, — новых данных всё равно не будет.


GET /api/v1/storages/{name}/config#

Чтение

Разобранная модель одного хранилища — то, что нужно для предзаполнения формы правки.

curl -sS "$BASE/api/v1/storages/local/config" -H "Authorization: Bearer $TOKEN"
{"name":"local",
 "config":{"name":"local","path":"/var/lib/hastreamer/dvr","identity":"",
           "segment_secs":900,"evict_at_percent":90,"s3":null}}
Ключ S3 возвращается в открытом виде

Для ярусов S3 поле secret_key приходит незашифрованным — осознанно: замена блока целиком обязана его вернуть. Не проксируйте этот ответ потребителю с меньшими правами.

Ошибки

Код Тело Когда
404 {"title":"not found","detail":"no storage \"cold\""} хранилища с таким именем нет в конфигурации

POST /api/v1/storages#

Запись

Добавляет блок storage и применяет конфигурацию. Успех — 201.

Тело запроса

Поле Тип Обязательно Смысл
name строка [A-Za-z0-9._-]+ да имя хранилища
arg2 строка да путь к каталогу либо литерал s3
directives строка нет тело блока: evict_at, параметры S3 и прочее
# Локальный диск
curl -sS -X POST "$BASE/api/v1/storages" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"dvr2","arg2":"/srv/dvr2","directives":"evict_at 85;"}'
# Холодный ярус S3
curl -sS -X POST "$BASE/api/v1/storages" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"cold","arg2":"s3",
       "directives":"endpoint s3.example.com;\nbucket archive;\naccess_key AKIAEXAMPLE;\nsecret_key ВАШ-КЛЮЧ;"}'

Ошибки

Код Когда
400 в теле нет ключа name
409 хранилище с таким именем уже есть
422 неверное имя, не передан arg2, путь не является одним токеном конфигурации, итог не разбирается
423 конфигурация заморожена

PATCH /api/v1/storages/{name}#

Запись

Заменяет тело блока хранилища целиком — передавайте его полностью, включая секреты S3.

curl -sS -X PATCH "$BASE/api/v1/storages/dvr2" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"directives":"evict_at 80;"}'

DELETE /api/v1/storages/{name}#

Запись

curl -sS -X DELETE "$BASE/api/v1/storages/dvr2" -H "Authorization: Bearer $TOKEN"

Примечания. Удаление, которое оставило бы пишущий поток без хранилища, отклоняется кодом 422: разбор конфигурации целиком выполняется до записи на диск. Файлы записей на диске при этом не удаляются.

Маршрут /storages/{name}/provision в этой сборке не работает

Он отвечает 400 {"title":"bad request","detail":"unknown verb \"provision\""} — разметка диска компилируется только в управляемой сборке. Не используйте его в интеграции: создавайте каталог средствами операционной системы и указывайте путь при создании хранилища.

Система, метрики и лицензия#

GET /api/v1/system#

Чтение

Снимок системного монитора, обновляемый примерно раз в секунду, плюс число рабочих ядер.

curl -sS "$BASE/api/v1/system" -H "Authorization: Bearer $TOKEN"
{"cpus":4,"worker_cores":4,
 "host_cpu_busy_pct":3.29,"per_core_busy_pct":[5.88,3.06,3.03,4.04],
 "proc_cpu_cores":0.1199,"rss_mb":153,
 "mem_total_mb":3910,"mem_available_mb":3259,
 "open_fds":61,"uptime_s":4077,
 "udp_ingest_kernel_drops":0,"udp_ingest_queue_dropped":0,
 "udp_ingest_recv_errs":0,"udp_ingest_rx_queued_kb":0,"udp_egress_bind_fails":0}
proc_cpu_cores — не то же самое, что %cpu из top

Это потребление CPU в ядрах, посчитанное как дельта процессорного времени, делённая на число ядер. Значение 0.12 означает «примерно одна восьмая ядра». Сравнивать его напрямую с мгновенной выборкой top не следует.

Ошибки

Код Когда
405 использован метод, отличный от GET

GET /api/v1/totals#

Чтение

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

curl -sS "$BASE/api/v1/totals" -H "Authorization: Bearer $TOKEN"
{"streams":10,"sessions":1,
 "bytes_in":6508772297,"bytes_out":105870921,
 "dvr_sessions":0,"dvr_bytes_out":24412877,
 "live_sessions":1,"live_bytes_out":81458044}

GET /api/v1/metrics#

Чтение

Весь снимок состояния одним ответом: массив потоков, массив сессий и агрегаты.

curl -sS "$BASE/api/v1/metrics" -H "Authorization: Bearer $TOKEN"
{"streams":[{"id":"cam1","core":0,"viewers":1,"bytes_in":4619859212,"fragments":18375,"…":"…"}],
 "sessions":[{"id":13,"stream":"cam1","protocol":"hls-fmp4","bytes_out":7364410,"…":"…"}],
 "totals":{"streams":10,"sessions":1,"bytes_in":6523182750,"bytes_out":106320842,
           "dvr_sessions":0,"dvr_bytes_out":24412877}}
Это не Prometheus и это не безлимитный дамп

GET /api/v1/metrics отдаёт JSON. Текстовая экспозиция Prometheus живёт по другому адресу — GET /metrics в корне хоста, вне /api/v1; см. Мониторинг и метрики.

Если суммарное число строк (потоки + сессии) превышает 2000, ответ будет 413 со счётчиками, а не молча усечённым телом:

{"error":"rollup too large — use the paginated /api/v1/streams and /api/v1/sessions",
 "streams":2400,"sessions":15000}

На масштабе пользуйтесь пагинированными списками. Кроме того, поле totals здесь у́же, чем ответ GET /totals: производных live_sessions и live_bytes_out в нём нет.


GET /api/v1/license#

Чтение

curl -sS "$BASE/api/v1/license" -H "Authorization: Bearer $TOKEN"
{"state":"licensed","customer":"example-customer","expires":"2027-01-31",
 "max_streams":50,"hwid":"<ОТПЕЧАТОК-ОБОРУДОВАНИЯ>"}

Отсутствующие поля опускаются, а не приходят как null: у бессрочной лицензии не будет ключа expires, у лицензии без ограничения — ключа max_streams. Состояния state: licensed — всё в порядке; grace — обновление лицензии не удаётся, но раздача продолжается; любое другое значение означает, что медиа не отдаётся. См. Лицензирование.

Журналы#

GET /api/v1/logs#

Чтение

Ограниченное кольцо операционных записей, новые первыми.

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

Параметр Тип и допустимые значения По умолчанию Смысл
level debug | info | warn | error debug минимальный уровень; неизвестное значение молча читается как debug
stream строка точное совпадение идентификатора потока
q подстрока без учёта регистра поиск по тексту записи
after целое 0 курсор «хвоста» — последний увиденный seq
limit целое, не больше 2000 500 сколько записей вернуть
curl -sS "$BASE/api/v1/logs?level=warn&limit=2" -H "Authorization: Bearer $TOKEN"
{"capture_level":"info","head_seq":17,
 "entries":[{"seq":17,"ts_ms":1787143225999,"level":"warn","target":"source",
             "stream":"cam1","msg":"[cam1] source stopped: error: Timeout"},
            {"seq":16,"ts_ms":1787143017551,"level":"warn","target":"source",
             "stream":"cam1","msg":"[cam1] source stopped: error: Timeout"}]}
capture_level — это порог записи, а не порог показа

То, что ниже capture_level, в кольцо никогда не попадало. Фильтр ?level=debug на сервере с capture_level: info не покажет отладочных записей — их просто нет. Чтобы они появились, поднимите подробность директивой log_level (применяется горячо) и подождите новых событий; уже записанное кольцо не пересобирается.

Чтение «хвоста». Опрашивайте маршрут, передавая в after наибольший seq из предыдущего ответа:

curl -sS "$BASE/api/v1/logs?after=17&limit=500" -H "Authorization: Bearer $TOKEN"

GET /api/v1/audit#

Чтение

Журнал административных действий, только дополняемый, новые первыми, со своим конвертом.

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

Параметр Тип и допустимые значения По умолчанию Смысл
cursor целое 0 0 — с самой свежей записи; иначе предыдущий next_cursor
limit целое 1 … 200 50 размер страницы
actor строка точное совпадение исполнителя
action строка точное совпадение действия
node строка точное совпадение имени узла
curl -sS "$BASE/api/v1/audit?limit=2" -H "Authorization: Bearer $TOKEN"
{"items":[{"id":42,"ts_ms":1787143225999,"actor":"admin","from":"10.0.0.55",
           "action":"stop","target":"cam1","status":200,"result":"ok"}],
 "limit":2,"next_cursor":41,"oldest":1,"tip":42}

Что записывается. Все императивные операции: жизненный цикл потока (create, clone, patch, delete, start, stop, restart), config-apply, config-rollback, config-globals, cert-upload, cert-delete, template-save, template-delete, vod-lib-save, vod-lib-delete, storage-create, storage-patch, storage-delete, kick, cordon, uncordon, drain, restart, rotate-session-keys, dvr-purge, dvr-candidates.

Чтения не записываются никогда. В поле result попадает только пояснение ошибки, но не тело ответа, поэтому секреты в журнал не утекают.

Куда дальше#