Records API
Records API предназначен для backend-to-backend доступа к архиву записей VideoGrace. Партнерский backend получает список завершенных recording jobs, метаданные записи и ссылки на артефакты в VideoGrace Storage.
API архива уже можно использовать в пилотной интеграции. Сам recorder пока развивается: записи могут состоять из отдельных дорожек участников, а готовый консолидированный файл конференции пока не является обязательным результатом.
Что доступно сейчас
Текущий контракт позволяет:
- получить до 500 последних записей;
- связать запись с конференцией по
conference_tag; - определить время, длительность, профиль и статус записи;
- получить
report.json,report.jsonl,manifest.jsonиevents.jsonl; - скачать сохраненные аудио- и видеодорожки;
- проверить размер и SHA-256 каждого файла;
- повторно читать архив независимо от истории CAN jobs.
Пока не входят в публичный контракт:
- консолидированный MP4, MKV или MP3 для всей конференции;
- получение одной записи по ID;
- серверная фильтрация и pagination;
- поиск записи по
external_idпартнерской комнаты; - удаление записи и управление retention через API;
- webhook
recording.ready; - создание и остановка 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
Recorder->>Storage: upload raw tracks and reports
Storage-->>Recorder: blob_id and URL
Recorder->>API: CAN artifact_ready
API->>Index: upsert recording by id
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.
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 авторизация не работает.
Временное правило доступа к файлам
Архив проверяет capability recordings.access, а Storage дополнительно
проверяет владельца blob-файла. В текущей версии service account партнера для
чтения файлов должен быть привязан к тому же техническому пользователю, от
имени которого recorder загружает артефакты.
Рекомендуемая конфигурация:
service account: recorder
-> client_id: <recorder technical user>
service account: partner-recordings
-> client_id: <the same recorder technical user>
-> separate client_secret
Не передавайте партнеру CAN token и пароль пользователя recorder.
Партнер получает только отдельный client_secret.
Получение архива
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",
"profile": "raw_tracks_v1",
"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": "tracks/audio/device_1002.ogg",
"blob_id": "8bc08b8c-58e1-4f90-8af0-8a29fbe04f52",
"url": "/api/storage/blobs/8bc08b8c-58e1-4f90-8af0-8a29fbe04f52",
"size": 364980,
"content_type": "audio/ogg",
"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. |
profile |
string | Профиль recorder, например raw_tracks_v1. |
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 для диапазона.
Не передавайте token в query string: URL может попасть в access log, историю браузера или систему аналитики.
Форматы recorder
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.
Консолидация
В текущем API запись не следует интерпретировать как один готовый фильм. Партнер должен:
- проверить
profile; - прочитать
manifest.jsonиreport.json; - показать пользователю доступные дорожки или обработать их своим pipeline;
- не ожидать обязательное поле
consolidated_url.
После появления консолидации она будет добавлена новым артефактом и новыми полями без удаления raw tracks.
Связь с партнерской комнатой
Integration API возвращает tag при создании комнаты. Сохраните его рядом с
external_id:
{
"external_id": "notabridge_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.
Polling
До появления 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.