Введение и аутентификация
Базовый путь /api/v1, вход по паролю, статические токены чтения и записи, форма ответов, коды ошибок и пагинация.
Всё, что делает панель управления, делается через REST API — панель не имеет ни одной привилегии,
недоступной вам из curl. Здесь описано то, что относится ко всем запросам: как получить токен,
как его передавать, как устроены ответы и как читать ошибки. Отдельные группы операций разобраны на
страницах Потоки и Конфигурация и система, готовые сценарии — на
странице Готовые сценарии.
Базовый путь и порты#
API живёт на том же HTTP-слушателе, что и панель, плеер и медиа — отдельный порт поднимать не нужно.
| Поверхность | Адрес | Директива |
|---|---|---|
| REST API | http://media.example.com:8080/api/v1 |
http <порт>; |
| То же поверх TLS | https://media.example.com/api/v1 |
https <порт>; |
| Метрики Prometheus | http://media.example.com:8080/metrics |
тот же слушатель |
Дальше в примерах используется переменная окружения BASE — задайте её один раз:
BASE=https://media.example.com
/api/v1GET /metrics — это корень хоста, а не /api/v1/metrics. Оба адреса существуют, но это разные
поверхности: в корне — текстовая экспозиция Prometheus, под /api/v1 — JSON-дамп. Учётные данные у
них общие. Подробности — Мониторинг и метрики.
Три вида учётных данных#
Все учётные данные задаются в блоке auth { } файла конфигурации и предъявляются одним и тем же
заголовком Authorization: Bearer <ТОКЕН>.
| Учётные данные | Директива | Что открывает |
|---|---|---|
| Сессия администратора | admin_password → POST /api/v1/auth/login |
всё: любой маршрут, любой метод |
| Статический токен записи | api_write_token |
все методы /api/v1/* и GET /metrics |
| Статический токен чтения | api_read_token |
только GET, HEAD, OPTIONS на /api/v1/* и GET /metrics |
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
admin_password |
строка, не пустая | не задан | горячее |
api_read_token |
строка, не короче 16 символов | не задан | горячее |
api_write_token |
строка, не короче 16 символов | не задан | горячее |
session_secret |
строка, не пустая | генерируется при старте | горячее |
admin_ip_bind |
флаг: admin_ip_bind; / admin_ip_bind off; |
off |
горячее |
Токен записи — строгое надмножество токена чтения. Интеграции, которая и читает, и изменяет, достаточно одного токена записи; службе наблюдения выдавайте токен чтения.
Пустое значение любого токена отвергается при проверке конфигурации, как и совпадение
api_read_token с api_write_token.
Если не задано ничего — ни пароль, ни один из токенов, — /api/v1/* открыт полностью: сервер
печатает предупреждение при старте, а POST /api/v1/auth/login отвечает
404 {"error":"auth disabled"}. Достаточно одной директивы из таблицы, чтобы гейт заработал.
Что не открывает ни один API-токен#
Статический токен никогда не становится ключом к воспроизведению: POST /api/v1/auth/media-grants
отвечает ему 403. Доступ к медиа — отдельный уровень, см. Модель доступа.
Вход и получение сессионного токена#
curl -sS -X POST "$BASE/api/v1/auth/login" \ -H 'Content-Type: application/json' \ -d '{"password":"ваш-пароль-администратора"}'
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIs…","exp":1787186134}
Поле exp — абсолютный дедлайн в секундах Unix. Время жизни токена — 12 часов. Ключ подписи
берётся из session_secret; если он не задан, секрет генерируется случайно при каждом старте, и
токены не переживают перезапуск сервиса.
Удобная заготовка для всех остальных примеров:
TOKEN=$(curl -sS -X POST "$BASE/api/v1/auth/login" \ -H 'Content-Type: application/json' \ -d '{"password":"ваш-пароль-администратора"}' \ | python3 -c 'import sys,json; print(json.load(sys.stdin)["token"])')
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}
Ответы входа при неудаче:
| Код | Тело | Причина |
|---|---|---|
| 400 | {"error":"need password"} |
в теле нет ключа password либо это не JSON |
| 401 | {"error":"invalid password"} |
пароль неверный |
| 404 | {"error":"auth disabled"} |
admin_password не задан — входить некуда |
| 413 | {"error":"body too large"} |
тело больше 4 КиБ |
| 429 | {"error":"too many failed logins","retry_after_ms":4000} |
окно торможения для этого адреса |
Неверный пароль отвечает 401 после безусловной задержки 600 мс, а адрес попадает в общеузловую таблицу торможения: пока окно открыто, запросы с него отсекаются кодом 429 ещё до чтения тела. Таблица общая для узла, поэтому открыть больше соединений не значит перебирать быстрее. Верный пароль сбрасывает состояние адреса.
Как передавать токен#
Только заголовком. Единственное исключение — поток событий:
# Правильно — всегда и везде: curl -sS "$BASE/api/v1/streams" -H "Authorization: Bearer $TOKEN" # Допустимо ТОЛЬКО для /api/v1/events и ТОЛЬКО с сессионным токеном: curl -sN "$BASE/api/v1/events?topics=totals&token=$TOKEN"
Строку запроса ?token= принимает единственный маршрут — GET /api/v1/events, потому что
браузерный EventSource не умеет задавать заголовки. На любом другом маршруте параметр игнорируется
и запрос получает 401.
api_read_token и api_write_token не принимаются в ?token= даже на /api/v1/events. Они
долгоживущие: URL с таким токеном оседает в журналах каждого прокси по пути и в истории браузера.
Для них — только заголовок Authorization.
Привязка сессии к адресу#
При auth { admin_ip_bind on; } сессионный токен привязан к IP-адресу, с которого выполнен вход, и
отвергается при предъявлении с другого адреса. Реальный адрес берётся из заголовков X-Forwarded-For,
Forwarded, X-Real-IP только если непосредственный клиент перечислен в trusted_proxies; иначе
используется адрес сокета. За обратным прокси без trusted_proxies все входы будут выглядеть как
приходящие с адреса прокси.
Обозначения прав в этой документации#
У каждой операции на страницах API стоит строка с требуемым уровнем:
Чтение — подходят любые учётные данные: сессия администратора, токен записи или токен чтения.
Запись — нужна сессия администратора либо api_write_token;
токен чтения получает 403.
Только сессия — принимается исключительно сессионный токен
из POST /auth/login; ни один статический токен не подходит.
Идентификатор потока в URL#
Идентификатор потока — непрозрачный путь, который может содержать / (cam1, live/cam1,
site-north/entrance/main). Отсюда одно правило, о которое спотыкаются все интеграции:
- В путях
/api/v1/…идентификатор передаётся одним percent-encoded сегментом:live%2Fcam1. - В медиа-URL идентификатор идёт с обычными слэшами:
/live/cam1/index.m3u8.
# Поток называется live/cam1 curl -sS "$BASE/api/v1/streams/live%2Fcam1" -H "Authorization: Bearer $TOKEN" # снимок curl -sS "$BASE/live/cam1/index.m3u8" # плейлист
Маршруты GET|PATCH|DELETE /streams/{id}, /dvr-archive/{stream} и все маршруты с {name} жёстко
отвергают литеральный / в сегменте. Маршруты действий (/start, /stop, /restart, /clone,
/config, /timeseries, /sessions) принимают обе формы, но кодировать безопаснее всегда.
В bash — python3 -c 'import sys,urllib.parse as u; print(u.quote(sys.argv[1], safe=""))' "live/cam1".
В Python — urllib.parse.quote(stream_id, safe="").
Форма ответа: конверта нет там, где вы его ждёте#
Ключ items — не универсальный. Формы ровно три.
| Форма | Кто так отвечает |
|---|---|
Конверт с пагинацией {"items":[…],"limit","total","raw_total","total_pages","next_cursor","page"} |
GET /streams, GET /sessions, GET /streams/{id}/sessions |
Собственный конверт {"items":[…],"next_cursor","oldest","tip","limit"} |
GET /audit |
Голый массив […] |
GET /storages, GET /config/streams, GET /config/history, GET /dvr-sync |
| Именованный объект | {"templates":[…]}, {"libraries":[…]}, {"certificates":[…]}, {"fields":[…]} |
Одиночные ресурсы (GET /streams/{id}, GET /system, GET /totals, GET /license) возвращают
объект без обёртки.
Пагинация, сортировка и проекция#
Параметры ниже понимают все три пагинированных списка.
| Параметр | Тип и допустимые значения | По умолчанию | Смысл |
|---|---|---|---|
limit |
целое, зажимается в 1 … 50000 |
100 |
размер страницы |
page |
целое ≥ 1 | — | номер страницы; приоритетнее cursor |
cursor |
строка из next_cursor предыдущего ответа |
— | keyset-курсор следующей страницы |
fields |
имена полей верхнего уровня через запятую | все поля | проекция элементов items |
sort |
до 4 ключей колонка или колонка:asc|desc через запятую |
по id |
сортировка |
dir |
asc | desc |
asc |
направление для ключей без явного суффикса |
curl -sS "$BASE/api/v1/streams?fields=id,health,viewers,bitrate&sort=viewers:desc&limit=3" \ -H "Authorization: Bearer $TOKEN"
{"items":[{"bitrate":2920000.0,"health":"ok","id":"cam1","viewers":1}, {"bitrate":14649134.86,"health":"ok","id":"cam2","viewers":1}, {"bitrate":3455232.76,"health":"ok","id":"cam3","viewers":0}], "limit":3,"next_cursor":"18446744073709551615\u001fcam3","page":null, "raw_total":10,"total":10,"total_pages":4}
Поле total — сколько строк осталось после фильтров, raw_total — сколько было до них. Значение
next_cursor подставляйте в ?cursor= следующего запроса; если оно null — курсорная пагинация для
этой сортировки недоступна, листайте через ?page=.
При сортировке по числовой колонке курсор склеен из двух частей служебным символом (\u001f в
JSON-ответе, %1F в URL). Подставляйте значение только после percent-кодирования: с сырым символом
curl отказывается собрать адрес (URL rejected: Malformed input to a URL function), а если
отбросить «хвост» курсора после служебного символа, следующая страница придёт с перекрытием —
последняя строка предыдущей повторится.
# Первая страница cur=$(curl -sS "$BASE/api/v1/streams?fields=id&limit=3" -H "Authorization: Bearer $TOKEN" \ | python3 -c 'import sys,json,urllib.parse; print(urllib.parse.quote(json.load(sys.stdin)["next_cursor"] or "", safe=""))') # Следующая curl -sS "$BASE/api/v1/streams?fields=id&limit=3&cursor=$cur" -H "Authorization: Bearer $TOKEN"
Читающие маршруты никогда не отвечают 4xx на неизвестное имя колонки или направление: неизвестная
колонка молча сводится к сортировке по умолчанию, неизвестное направление читается как asc. Если
порядок вышел не тот, что вы ждали, — проверьте написание имени колонки, ошибки в ответе не будет.
Условные запросы#
Списки потоков и счётчики снабжены ETag. Присылайте If-None-Match — при совпадении сервер ответит
304 и не будет строить тело.
Ошибки#
Форм ошибок три, и они не смешиваются.
1. application/problem+json — все 404 на /api/* и все ошибки изменяющих операций:
{"type":"about:blank","title":"Not Found","status":404,"instance":"/api/v1/streams/nosuchstream"}
Поле type всегда about:blank, title — короткая машинно-стабильная формулировка, detail —
пояснение для человека (у чтений отсутствует).
2. application/json {"error":"<тег>"} — маршруты /auth/* и любой отказ гейта:
{"error":"unauthorized"}
3. Короткий text/plain — только медиа-маршруты; плееры не читают тело.
Коды, которые вернёт любая операция#
| Код | Форма | Когда |
|---|---|---|
401 |
{"error":"unauthorized"} |
токена нет, он повреждён, истёк или предъявлен с чужого адреса при admin_ip_bind on |
403 |
{"error":"forbidden","reason":"…"} |
учётные данные подлинные, но уровня недостаточно |
404 |
problem+json |
ресурса нет либо маршрут не существует в этой сборке |
405 |
text/plain |
метод не поддерживается маршрутом |
409 |
problem+json |
имя занято либо конфигурация изменилась под вами |
413 |
problem+json / {"error":…} |
тело больше лимита маршрута |
422 |
problem+json |
правка не проходит проверку конфигурации |
423 |
problem+json |
конфигурация заморожена файлом <config>.locked |
501 |
problem+json |
сервер запущен без файла конфигурации |
Ответ 401 предложил бы корректному клиенту повторить запрос с тем же токеном — и так бесконечно.
Поэтому при подлинном, но недостаточном токене приходит 403 с полем reason:
reason |
Что произошло |
|---|---|
read_token_cannot_write |
токеном чтения выполнен небезопасный метод |
read_token_cannot_read_config |
токеном чтения запрошен GET /api/v1/config |
api_token_is_not_a_media_credential |
статическим токеном запрошен медиа-грант |
Само значение токена в reason никогда не попадает. Сессия администратора этих ограничений не знает.
Ограничения транспорта#
| Лимит | Значение |
|---|---|
Тело POST /auth/login и /auth/media-grants |
4 КиБ → 413 |
| Тело изменений конфигурации, потоков и хранилищ | 1 МиБ → 413 |
Тело GET /api/v1/metrics (JSON-дамп) |
2000 строк потоков и сессий → 413 |
| Запросов на одно keep-alive-соединение | 1000 |
| Таймаут первого заголовка и простоя keep-alive | 15 с |
Машиночитаемая спецификация#
Полная спецификация OpenAPI 3.1 со всеми 71 операцией, схемами тел, примерами и кодами ответов:
Страницы этого раздела описывают операции над потоками, конфигурацией и системой. Всё остальное — сессии целиком, шаблоны, библиотеки VoD, обслуживание архива, кластер и действия узла — есть в спецификации; её можно открыть в любом просмотрщике OpenAPI или скормить генератору клиента.
Куда дальше#
- Потоки — весь жизненный цикл потока через API.
- Конфигурация и система — чтение и запись конфигурации, лицензия, журналы.
- Готовые сценарии — рабочие рецепты на
curlи Python. - Доступ к админке и API — как устроен гейт со стороны конфигурации.