Records API
Records API предназначен для backend-to-backend доступа к архиву записей VideoGrace. Партнерский backend получает список завершенных recording jobs, метаданные записи и ссылки на артефакты в VideoGrace Storage.
Основной профиль recorder consolidated_native создает готовый единый
Matroska-файл конференции с H.264-видео и Opus-звуком. Диагностические профили
также могут сохранять отдельные дорожки участников.
Что доступно сейчас
Текущий контракт позволяет:
- получить до 500 последних записей;
- связать запись с конференцией по
conference_tag; - определить время, длительность, профиль и статус записи;
- получить консолидированный
recording.mkv,report.json,report.jsonlиevents.jsonl; - скачать готовую запись или диагностические аудио- и видеодорожки;
- проверить размер и SHA-256 каждого файла;
- получить подписанный webhook
recording.readyпосле upload и индексации; - повторно читать архив независимо от истории CAN jobs.
Пока не входят в публичный контракт:
- консолидированный MP4 и MP3 для всей конференции;
- получение одной записи по ID;
- серверная фильтрация и pagination;
- поиск записи по
external_idпартнерской комнаты; - удаление записи и управление retention через API;
- создание и остановка recording job через партнерский Records API.
Дополнительные поля могут появляться без изменения версии API. Клиент должен игнорировать неизвестные поля.
Общая схема
sequenceDiagram
participant Partner as Partner backend
participant API as VideoGrace API
participant Recorder as Recorder worker
participant Storage as VideoGrace Storage
participant Index as records.db
participant Webhook as Partner webhook
Recorder->>Storage: upload recording.mkv and reports
Storage-->>Recorder: blob_id and URL
Recorder->>API: CAN artifact_ready
API->>Index: upsert recording by id
API->>Webhook: recording.ready
Partner->>API: GET /api/v1.0/recordings
API-->>Partner: recordings and artifact URLs
Partner->>Storage: GET /api/storage/blobs/{blob_id}
Storage-->>Partner: file or byte range
records.db является постоянным индексом архива. Очистка старых CAN jobs не
удаляет записи из Records API.
Базовый адрес
https://<server>
Архив:
GET /api/v1.0/recordings
Файлы:
GET /api/storage/blobs/{blob_id}
Используйте только HTTPS.
Авторизация
Для партнерской интеграции создается отдельный service account. Capability
recordings.access нужна ему для просмотра полного архива через Records API.
Для приема recording.ready и скачивания артефактов собственных партнерских
комнат это глобальное право не требуется.
Access token можно получить через Integration API:
POST /api/v1/integration/auth/token
Content-Type: application/json
{
"service_account": "partner-recordings",
"client_secret": "<secret>"
}
Ответ:
{
"result": 200,
"access_token": "<jwt>",
"token_type": "Bearer",
"expires_in": 604800,
"refresh_token": null
}
refresh_token в текущей версии не выдается. После истечения token backend
повторяет POST /api/v1/integration/auth/token.
Во всех запросах используйте:
Authorization: Bearer <access_token>
JWT должен быть подписан серверным VG_JWT_SECRET. Без настроенного JWT
service-account авторизация не работает.
Доступ к файлам
После индексации сервер помечает загруженные артефакты scope
recording:<conference_tag>. Такой приватный blob может скачать его владелец,
владелец соответствующей Integration API room или технический пользователь с
capability recordings.access. Поэтому партнерский service account не нужно
привязывать к пользователю recorder. Для скачивания URL из recording.ready
владельцу партнерской комнаты глобальный grant recordings.access не нужен.
Не передавайте партнеру CAN token, service secret recorder или пароль его
технического пользователя. Партнер получает собственный client_secret.
Capability recordings.access выдавайте связанному пользователю только тогда,
когда backend действительно должен читать полный Records API.
Получение архива
GET /api/v1.0/recordings
Authorization: Bearer <access_token>
Accept: application/json
Пример:
curl -sS \
-H "Authorization: Bearer $VG_ACCESS_TOKEN" \
-H "Accept: application/json" \
https://join.example.com/api/v1.0/recordings
Успешный ответ:
{
"ok": true,
"recordings": [
{
"id": "19f7010bc231",
"job_id": "19f7010bc231",
"conference_tag": "lesson-a8f3k2",
"conference_session_id": 1842,
"profile": "consolidated_native",
"status": "completed",
"started_at_ms": 1783952338332,
"ended_at_ms": 1783952395816,
"duration_ms": 57484,
"output_dir": "recordings/lesson-a8f3k2/19f7010bc231",
"report_blob_id": "e8a82165-331d-41bb-887c-9abb240c8c70",
"report_url": "/api/storage/blobs/e8a82165-331d-41bb-887c-9abb240c8c70",
"report_jsonl_blob_id": "f805d628-2998-49f2-b089-c9a405683826",
"report_jsonl_url": "/api/storage/blobs/f805d628-2998-49f2-b089-c9a405683826",
"manifest_blob_id": "1f0287b8-d88a-43d6-b29c-ab89522893db",
"manifest_url": "/api/storage/blobs/1f0287b8-d88a-43d6-b29c-ab89522893db",
"events_blob_id": "fc9848a4-3ff5-48d9-b9e0-d2b5e6195b41",
"events_url": "/api/storage/blobs/fc9848a4-3ff5-48d9-b9e0-d2b5e6195b41",
"files": [
{
"path": "recording.mkv",
"blob_id": "8bc08b8c-58e1-4f90-8af0-8a29fbe04f52",
"url": "/api/storage/blobs/8bc08b8c-58e1-4f90-8af0-8a29fbe04f52",
"size": 364980,
"content_type": "video/x-matroska",
"sha256": "157579d36ac791d840fa26862b1f85c271f9f869c6e1b4b09d5dfbd96ff12219",
"scope": "manual"
}
],
"created_at": 1783952396,
"updated_at": 1783952396,
"artifact": {}
}
]
}
Записи отсортированы от новых к старым. Сервер возвращает не более 500 элементов. Query-параметры фильтрации в текущей версии не поддерживаются.
Поле db_path, если присутствует в ответе, является диагностическим и не
должно использоваться партнерским клиентом.
Поля записи
| Поле | Тип | Описание |
|---|---|---|
id |
string | Стабильный ID записи. Обычно совпадает с job_id. |
job_id |
string | ID recording job в CAN. |
conference_tag |
string | Тег конференции VideoGrace. |
conference_session_id |
integer | Точный ID сеанса конференции из stats.db. Используйте его для связи записи со статистикой и расшифровкой; тег обозначает комнату и может повторяться между сеансами. Для старых записей без достоверной связи равен 0. |
profile |
string | Профиль recorder, обычно consolidated_native. |
status |
string | Итоговый статус recorder. Значение следует считать расширяемым. |
started_at_ms |
integer | Начало записи, Unix time в миллисекундах. |
ended_at_ms |
integer | Завершение записи, Unix time в миллисекундах. |
duration_ms |
integer | Длительность записи в миллисекундах. |
output_dir |
string | Внутренний путь worker. Не использовать для скачивания. |
report_blob_id |
string | ID итогового report.json. |
report_url |
string | URL итогового report.json. |
report_jsonl_blob_id |
string | ID append-only отчета report.jsonl. |
report_jsonl_url |
string | URL report.jsonl. |
manifest_blob_id |
string | ID track manifest. |
manifest_url |
string | URL track manifest. |
events_blob_id |
string | ID журнала событий. |
events_url |
string | URL журнала событий. |
files |
array | Загруженные файлы записи. |
artifact |
object | Исходный расширяемый payload artifact_ready. |
created_at |
integer | Создание записи в индексе, Unix time в секундах. |
updated_at |
integer | Последнее обновление индекса, Unix time в секундах. |
Пустая строка означает, что соответствующий артефакт recorder не создал или не загрузил.
Статусы
Сейчас обычно встречаются:
| Статус | Значение |
|---|---|
completed |
Recorder завершил job штатно. |
stopped |
Запись остановлена пользователем или отменой job, отчет сохранен. |
Не используйте закрытый enum на стороне партнера. Новые статусы должны
отображаться как неизвестное строковое значение, а не приводить к ошибке
десериализации.
Файлы записи
Каждый элемент files содержит:
| Поле | Тип | Описание |
|---|---|---|
path |
string | Логический путь файла внутри записи. |
blob_id |
string | ID объекта в VideoGrace Storage. |
url |
string | Относительный или абсолютный URL скачивания. |
size |
integer | Размер в байтах. |
content_type |
string | MIME type. |
sha256 |
string | SHA-256 содержимого в hex. |
scope |
string | Storage scope. Диагностическое поле. |
Если URL относительный, его нужно разрешить относительно базового адреса VideoGrace:
/api/storage/blobs/abc
-> https://join.example.com/api/storage/blobs/abc
Не формируйте URL из output_dir.
Скачивание
curl -fL \
-H "Authorization: Bearer $VG_ACCESS_TOKEN" \
-o device_1002.ogg \
"https://join.example.com/api/storage/blobs/8bc08b8c-58e1-4f90-8af0-8a29fbe04f52"
После скачивания рекомендуется проверить размер и SHA-256:
test "$(wc -c < device_1002.ogg | tr -d ' ')" = "364980"
echo "157579d36ac791d840fa26862b1f85c271f9f869c6e1b4b09d5dfbd96ff12219 device_1002.ogg" \
| sha256sum -c -
Storage поддерживает HTTP Range:
GET /api/storage/blobs/{blob_id}
Authorization: Bearer <access_token>
Range: bytes=0-1048575
Успешный ответ будет иметь HTTP 200 для полного файла или 206 Partial
Content для диапазона. Поддерживаются закрытый (bytes=0-1048575), открытый
(bytes=1048576-) и suffix-диапазон (bytes=-65536) для чтения индекса
медиаконтейнера с конца файла. Некорректный или составной диапазон возвращает
416 Range Not Satisfiable.
Не передавайте token в query string: URL может попасть в access log, историю браузера или систему аналитики.
Форматы recorder
consolidated_native
Основной production-профиль. Python CAN worker запускает нативный Recorder, который использует общий с Consolidator pipeline раскладки, VAD, модерации, микширования и кодирования. Результат:
recording.mkv: единый H.264 + Opus файл конференции;events.jsonl: события lifecycle;report.jsonиreport.jsonl: отчет и журнал финализации.
Нативный процесс не выполняет upload самостоятельно. Python worker после
финализации файла загружает его в Storage и отправляет artifact_ready.
Локальный каталог job удаляется только после успешной загрузки всех артефактов
и подтвержденного CAN complete. Ошибка upload или CAN сохраняет файлы на
узле для повторной доставки и диагностики.
raw_tracks_v1
Профиль хранит отдельные дорожки по device_id:
- аудио: Ogg/Opus, обычно
tracks/audio/device_<id>.ogg; - камера: H.264 Annex-B или IVF/VP8;
- демонстрация экрана: отдельная видеодорожка;
manifest.json: описание дорожек, codec и lifecycle;events.jsonl: события конференции, устройств и VAD;report.json: итоговый снимок recording job;report.jsonl: append-only журнал финализации.
Наличие конкретного типа дорожки зависит от устройств, активных во время конференции.
decoded_audio
Профиль может содержать декодированные WAV или MP3-сегменты. Состав файлов определяется параметрами recorder job.
Выбор результата
Партнер должен проверить profile. Для consolidated_native основным media
artifact является recording.mkv в массиве files. Для raw_tracks_v1
следует читать manifest.json и обрабатывать отдельные дорожки. Не выбирайте
файл только по его позиции в массиве: используйте path и content_type.
Связь с партнерской комнатой
Integration API возвращает tag при создании комнаты. Сохраните его рядом с
external_id:
{
"external_id": "partner_lesson_987",
"tag": "lesson-a8f3k2"
}
Records API возвращает этот же tag в conference_tag. Текущая схема связи:
partner lesson external_id
-> saved VideoGrace room tag
-> recording.conference_tag
Не пытайтесь извлекать external_id из conference_tag.
Webhook recording.ready
Для партнерских комнат рекомендуется настроить webhook через Integration API:
PUT /api/v1/integration/webhook
Authorization: Bearer <access_token>
Content-Type: application/json
{
"url": "https://partner.example.com/webhooks/videograce",
"secret": "replace-with-a-random-secret",
"events": ["recording.ready"],
"enabled": true
}
Событие ставится в персистентную очередь после завершения загрузки файлов и
успешного commit записи в records.db. Payload содержит external_id,
conference_tag, метаданные записи, список файлов и URL артефактов. Формат
подписи, retry policy и полный пример payload описаны в разделе
Integration API: Webhook готовности записи.
Получатель должен дедуплицировать повторы по полю id или заголовку
X-VideoGrace-Delivery.
Polling как fallback
Если webhook временно недоступен или интеграция еще не переведена на события, можно использовать polling:
- Выполнять
GET /api/v1.0/recordingsраз в 10-30 секунд, пока ожидается запись. - Индексировать элементы по
id. - Обновлять существующий элемент, если изменился
updated_at. - Считать запись доступной, когда нужные URL и
filesуже присутствуют. - Использовать exponential backoff при HTTP
5xx.
artifact_ready индексируется после загрузки файлов. Между остановкой записи и
ее появлением в API возможна задержка на финализацию и upload.
Ошибки
Архив
| HTTP | Причина |
|---|---|
200 |
Запрос выполнен. Проверить ok. |
401 |
Token отсутствует, истек или невалиден. |
403 |
У пользователя нет recordings.access. |
Если индекс недоступен, сервер может вернуть HTTP 200:
{
"ok": false,
"error": "database error",
"recordings": []
}
Клиент должен проверять не только HTTP status, но и ok.
Storage
| HTTP | Причина |
|---|---|
200 |
Полный файл. |
206 |
Диапазон файла. |
401 |
Token не передан. |
403 |
Token не имеет доступа к blob. |
404 |
Blob не найден. |
416 |
Некорректный Range. |
При 401 получите новый access token. При 403 проверьте привязку service
account к техническому пользователю recorder.
Рекомендуемый flow партнера
flowchart TD
Token[Получить access token] --> Rooms[Получить список записей]
Rooms --> Match[Найти conference_tag]
Match --> Ready{Есть files и report_url?}
Ready -->|Нет| Wait[Повторить polling позже]
Wait --> Rooms
Ready -->|Да| Report[Скачать report.json]
Report --> Manifest[Скачать manifest.json при наличии]
Manifest --> Files[Скачать нужные files]
Files --> Verify[Проверить size и sha256]
Verify --> Publish[Показать запись или передать в обработку]
Минимальный smoke-тест
BASE_URL=https://join.example.com
ACCESS_TOKEN="$(
curl -fsS "$BASE_URL/api/v1/integration/auth/token" \
-H 'Content-Type: application/json' \
-d '{
"service_account": "partner-recordings",
"client_secret": "'"$VG_RECORDINGS_CLIENT_SECRET"'"
}' |
python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])'
)"
curl -fsS "$BASE_URL/api/v1.0/recordings" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Accept: application/json' |
python3 -m json.tool
Для полного smoke-теста возьмите первый непустой report_url, разрешите его
относительно BASE_URL и скачайте с тем же bearer token.
Версионирование
Текущий endpoint использует ветку API v1.0. В рамках совместимых изменений
сервер может:
- добавлять поля;
- добавлять новые значения
statusиprofile; - добавлять новые типы файлов;
- возвращать новые объекты внутри
artifact.
Несовместимые изменения должны публиковаться в новом endpoint или с новым явным version contract.