RTC HTTP API: continuous recording, archives, playback, and download
Use the public contract matching the deployment version. roomId identifies a meeting; recordingId identifies one recording run, and a room can have several runs. The current host or a business API with rtc.manage controls capture. Historical viewing uses separate rtc.recordings.view/play/download grants or a short-lived archive ticket issued by an authorized backend. An in-meeting RTC token cannot replace an archive ticket.
During the meeting: allow, start, query, stop
| Endpoint | Input | Result/check | Grant and edge case |
|---|---|---|---|
PUT /api/v2/rtc/rooms/{roomId}/recording-policy |
JSON { "enabled": true/false } |
Room policy | Organizer or super-admin. Allowing recording does not start it. |
POST /api/v2/rtc/rooms/{roomId}/recording/continuous/start |
Room ID, no body | recordingId, Starting/Recording, time origin |
rtc.manage. Repeated start in one room returns its active run. |
GET /api/v2/rtc/rooms/{roomId}/recording/continuous/current |
Room ID | Active run or empty | Empty means no active recording, not no history. |
POST /api/v2/rtc/rooms/{roomId}/recording/continuous/{recordingId}/stop |
Specific run ID | Stopping, later Stopped | rtc.manage. Original files may still be finalizing while Stopping. |
POST /api/v2/rtc/session/rooms/{roomId}/recording/continuous/start / .../{recordingId}/stop |
In-meeting device token; run ID for stop | Same run state | Current host only; former host gets 403 after transfer. |
GET /api/v2/rtc/session/rooms/{roomId}/recording/continuous/current |
Current device token | State or empty | An admitted ordinary member may know recording is active. |
GET /api/v2/rtc/rooms/{roomId}/recording/continuous/runs / GET /api/v2/rtc/session/rooms/{roomId}/recording/continuous/runs |
Room and corresponding backend/archive identity | Historical run list | Different from active-current; archive grant filters scope. |
Legacy recording/start, recording/stop, and recording/targets operate per publication and do not replace whole-meeting recording. During the meeting, original audio and video are copied in their existing encoding as independent tracks. Member/screen playback copies and a full-meeting MP4 are built afterward. Muted or disconnected periods keep real gaps rather than replaying frozen media.
Backend: issue a room-bound archive ticket
POST /api/v2/rtc/rooms/{roomId}/recordings/access
Authorization: Bearer <backend platform session or API token>
Content-Type: application/json
{"actions":["view","play","download"],"lifetimeSeconds":120}
actions accepts only view/play/download. Default is view+play; requesting play or download also includes view. Lifetime defaults to 120 seconds and is clamped to 30–600. Response includes token, roomId, effective actions, expiresAtUtc. Issuer cannot grant beyond its own rights. Each use rechecks issuing session/API token, account, permissions, and room access. Give the short ticket only to the authorized client; keep the long-lived management token on the backend.
| Action | Permission | Allowed | Not allowed |
|---|---|---|---|
view |
rtc.recordings.view |
Catalog, manifest, public events, attachment preview | Media playback or ZIP download. |
play |
rtc.recordings.play, plus view |
HTTP Range media and full-export MP4 playback | Not a download grant. |
download |
rtc.recordings.download, plus view |
Files, attachments, ZIP, full-export download | Other rooms and direct messages. |
Use Authorization: Bearer <archive ticket> for JSON. Returned short-lived resource URLs may contain rtc_archive_token; they are restricted to permitted read paths and expire. Do not forward them publicly. rtc.manage enables administrative paths; client tickets should carry minimum actions.
Query runs, timeline, and devices
| Endpoint | Input | Return | Edge case |
|---|---|---|---|
GET /api/v2/rtc/recordings |
roomId, search, state, fromUtc/toUtc, participantId/deviceSessionId, page/pageSize |
total/page/pageSize/items[] with run ID, state, origin, participant/device counts |
Page size 1–100. UTC time range filters run starts (start inclusive, end exclusive). Grant filtering precedes count/paging. |
GET /api/v2/rtc/session/rooms/{roomId}/playback/recordings/{recordingId}/manifest |
afterSequence, maxEvents |
originAtUtc, durationMilliseconds, tracks, composites, events, hasMoreEvents/nextSequence |
Events are paged. Keep reading by nextSequence; file and event offsets share one origin. |
GET .../full-exports |
Run ID | Export IDs, ranges, states, files | Pending/Running/Failed has no valid play URL. |
The full prefix of GET .../full-exports is /api/v2/rtc/session/rooms/{roomId}/playback/recordings/{recordingId}. Tracks are separated by participantId + deviceSessionId + mediaKind; a rejoin may create another device session, which is not a hardware ID. Events contain only public chat, board, controls, and speaker activity permitted in this archive. Direct messages/attachments do not become public because a viewer is a host; missing old events should be shown as unknown, not inferred.
Media, attachments, ZIP, and full export
All read routes below share /api/v2/rtc/session/rooms/{roomId}/playback/recordings/{recordingId} and recheck that a file or attachment really belongs to this room and run.
| Method/suffix | Action | Return/purpose | Edge case |
|---|---|---|---|
GET /files/{fileId}/media |
play |
Original track or member copy, HTTP Range | Preserve player position and refresh URL on expiry. |
GET /files/{fileId}/download |
download |
File attachment response | Guessing another run's file ID does not grant access. |
GET /attachments/{attachmentId}/content |
view |
Public image/video/PDF/text preview | Other formats may yield 415; direct/out-of-range references remain private. |
GET /attachments/{attachmentId}/download |
download |
Permitted public attachment | Original meeting visibility still applies. |
GET /package |
download |
Streamed ZIP with original tracks, copies, public events/attachments | Missing attachments are declared, not fabricated. |
GET /full-exports/{exportId}/media / /download |
play / download |
Available fixed-layout MP4; playback supports Range | Only an Available export may be read. |
An authorized backend queues a full export:
POST /api/v2/rtc/rooms/{roomId}/recording/continuous/{recordingId}/full-exports
Authorization: Bearer <business API token with rtc.manage>
Content-Type: application/json
{"startMilliseconds":0,"endMilliseconds":60000}
The range is in milliseconds from the run origin, with start < end inside its duration. Query the returned export job until complete. Failed post-meeting member/screen copies and exports have management retry routes .../composites/{compositeId}/retry and .../full-exports/{exportId}/retry, still requiring original tracks and grant. Full MP4 creation does not add continuous live transcoding.
Failure handling
| Result | Action |
|---|---|
| 401 | Ticket expired or issuing credentials revoked. Get a fresh backend ticket, not an RTC join token. |
| 403 | Missing action or current account has no room archive grant. Stop retries and request access. |
| 404 | Room, run, file, or attachment is outside the same visible scope. Refresh catalog/manifest. |
| 415 | Unsupported inline attachment preview. Use download only if granted. |
| Starting/Recording/Stopping | Live run states. Wait for Stopped and indexed files after Stopping. |
| Pending/Running/Failed/Available | Copy/export states. Offer playback only for Available. |
See the UI-free archive SDK reference for Web, Android, iOS, UniApp methods and speaker-event replay.