Recording Management¶
This section details the RPC-style endpoints used to manage conference recordings (Start, Stop, List, Download, Presence Tracking).
Recordings are persisted in the database with participant tracking, quota enforcement, and role-based access control. Files are stored on Cloudflare R2 with presigned download URLs.
Base URLs:
- Start/Stop: /api/v1/livekit
- List/Download/Presence: /api/v1/recordings
Authentication: Required (Session Cookie)
Start Recording¶
Initiates a room composite egress recording for a LiveKit conference room. Creates a recording entry in the database, enforces the organization's monthly recording quota, and registers the initial participants.
- URL:
/start-recording - Method:
POST - Permission:
StartRecording(Admin, Moderator)
Request Body¶
| Field | Type | Required | Description |
|---|---|---|---|
roomName |
string | Yes | The LiveKit room name (room UUID from the rooms table). |
conferenceTopic |
string | Yes | Display name of the conference/meeting being recorded. |
zoneId |
string | Yes | Zone identifier within the room where the recording takes place. |
participants |
string | Yes | Comma-separated display names of participants (used for the recording template URL). |
participantIds |
string[] | Yes | WorkOS user IDs of the participants present in the zone at recording start. These are tracked in the recording_participants table. |
Example Request:
{
"roomName": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"conferenceTopic": "Sprint Planning",
"zoneId": "zone-meeting-room-1",
"participants": "Alice, Bob, Charlie",
"participantIds": [
"user_01ABC",
"user_02DEF",
"user_03GHI"
]
}
Response (200 OK)¶
{
"egressId": "EG_KvVaQYriPYxP",
"timestamp": 1706745600
}
Errors¶
| Code | Reason |
|---|---|
400 |
Invalid or malformed JSON request body. |
401 |
Missing or invalid authentication token. |
402 |
Organization's monthly recording quota has been exceeded. |
403 |
User does not have the StartRecording permission (RBAC). |
409 |
A recording is already active for this room and zone. |
422 |
Validation error: one or more required fields are missing or invalid. |
500 |
Internal server error (egress start failure, database error, metadata update failure). |
Notes¶
- The recording file is stored at
/{workos_org_id}/{roomName}/{timestamp}.mp4on R2. - If the egress fails to start or metadata update fails, the recording row is cleaned up automatically.
- Initial participants are added in parallel; individual failures are logged but do not block the recording.
- Quota is calculated from
SUM(duration_seconds)of all recordings in the current billing cycle.
Stop Recording¶
Stops an ongoing recording by its egress ID. The final status, duration, and expiry are updated asynchronously via the egress_ended LiveKit webhook.
- URL:
/stop-recording - Method:
POST - Permission:
StopRecording(Admin, Moderator)
Request Body¶
| Field | Type | Required | Description |
|---|---|---|---|
egressId |
string | Yes | The egress ID returned from the start recording call. |
zoneId |
string | Yes | Zone identifier where the recording is active. |
roomName |
string | Yes | The LiveKit room name (room UUID). |
Example Request:
{
"egressId": "EG_KvVaQYriPYxP",
"zoneId": "zone-meeting-room-1",
"roomName": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Response (200 OK)¶
{
"egressId": "EG_KvVaQYriPYxP",
"timestamp": 1706745600
}
Errors¶
| Code | Reason |
|---|---|
400 |
Invalid or malformed JSON request body. |
401 |
Missing or invalid authentication token. |
403 |
User does not have the StopRecording permission (RBAC). |
409 |
No active recording found for this room and zone. |
422 |
Validation error: one or more required fields are missing or invalid. |
500 |
Internal server error (egress stop failure, metadata update failure). |
Notes¶
- Stopping the egress triggers a LiveKit
egress_endedwebhook, which asynchronously updates the recording row withstatus,ended_at,duration_seconds, andexpires_at. - The
expires_atis calculated asended_at + RECORDING_RETENTION_HOURS.
List Recordings¶
Returns a paginated list of recordings for the authenticated user's organization. Access is role-based: Admin and Moderator see all recordings; Members see only recordings they participated in.
- URL:
/list - Method:
POST - Permission: Authenticated User
Request Body¶
| Field | Type | Required | Validation | Description |
|---|---|---|---|---|
limit |
int | Yes | min=1, max=100 |
Number of recordings to return. |
offset |
int | Yes | min=0 |
Number of recordings to skip (for pagination). |
Example Request:
{
"limit": 20,
"offset": 0
}
Response (200 OK)¶
{
"recordings": [
{
"id": 42,
"egressId": "EG_KvVaQYriPYxP",
"roomId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"conferenceTopic": "Sprint Planning",
"zoneId": "zone-meeting-room-1",
"initiatedBy": "user_01ABC",
"durationSeconds": 3600,
"status": "completed",
"startedAt": "2025-01-31T12:00:00Z",
"endedAt": "2025-01-31T13:00:00Z",
"expiresAt": "2025-02-01T13:00:00Z",
"participants": [
{ "workosUserId": "user_01ABC" },
{ "workosUserId": "user_02DEF" },
{ "workosUserId": "user_03GHI" }
]
}
]
}
Response Fields¶
| Field | Type | Description |
|---|---|---|
id |
int64 | Internal recording ID. |
egressId |
string | LiveKit egress identifier. |
roomId |
string (UUID) | Room UUID from the rooms table (also used as the LiveKit room name). |
conferenceTopic |
string | Display name of the conference. |
zoneId |
string | Zone identifier within the room. |
initiatedBy |
string | WorkOS user ID of the user who started the recording. |
durationSeconds |
int | null | Recording duration in seconds. null if still active. |
status |
string | One of: active, completed, failed. |
startedAt |
string (ISO 8601) | When the recording started. |
endedAt |
string | null | When the recording ended. null if still active. |
expiresAt |
string | null | When the recording file expires and becomes unavailable for download. |
participants |
object[] | List of participants with their workosUserId. The frontend resolves display names and avatars from its local cache. |
Errors¶
| Code | Reason |
|---|---|
400 |
Invalid or malformed JSON request body, or organization could not be resolved. |
401 |
Missing or invalid authentication token. |
422 |
Validation error: limit or offset out of range. |
500 |
Internal server error (database query failure). |
Access Control¶
| Role | Visibility |
|---|---|
| Admin | All recordings in the organization. |
| Moderator | All recordings in the organization. |
| Member | Only recordings where the user is a participant. |
Notes¶
- Returns all recordings that are either currently active (
status = 'active') or have not yet expired (expires_at > NOW()). Completed and failed recordings remain visible until theirexpires_athas passed. - Results are ordered by
started_at DESC(most recent first).
Get Download Link¶
Generates a presigned URL to download a completed recording from Cloudflare R2. Access is role-based with participant verification for regular members.
- URL:
/download-link - Method:
POST - Permission: Authenticated User (with access control)
Request Body¶
| Field | Type | Required | Description |
|---|---|---|---|
recordingId |
int64 | Yes | The ID of the recording to download. |
Example Request:
{
"recordingId": 42
}
Response (200 OK)¶
{
"url": "https://r2.example.com/out/org_01ABC/a1b2c3d4/2025-01-31T12:00:00Z.mp4?X-Amz-Signature=...",
"expiresAt": "2025-02-01T13:00:00Z"
}
Response Fields¶
| Field | Type | Description |
|---|---|---|
url |
string | Presigned S3-compatible GET URL for the recording file. Valid until expiresAt. |
expiresAt |
string (ISO 8601) | When the presigned URL (and the recording file itself) expires. |
Errors¶
| Code | Reason |
|---|---|
400 |
Invalid request, recording not found in this organization, or recording status is not completed (still active or failed). |
401 |
Missing or invalid authentication token. |
403 |
User is not a participant in this recording (Members only; Admin/Moderator bypass this check). |
410 |
Recording has expired (expires_at is in the past). The file is no longer available for download. |
422 |
Validation error: recordingId is missing or invalid. |
500 |
Internal server error (presigned URL generation failure, database error). |
Access Control¶
| Role | Access |
|---|---|
| Admin | Any recording in the organization. |
| Moderator | Any recording in the organization. |
| Member | Only recordings where the user is a participant (recording_participants table). |
Notes¶
- The presigned URL TTL matches the remaining time until the recording's
expires_at. If noexpires_atis set, the TTL defaults toRECORDING_RETENTION_HOURS. - A recording must have
status = "completed"to be downloadable. Active or failed recordings return400. - Once
expires_athas passed, the endpoint returns410 Gone.
Update Presence¶
Registers the authenticated user as a participant in the active recording for a given room and zone. Called by the client when a user enters a zone that has an active recording.
- URL:
/update-presence - Method:
POST - Permission: Authenticated User
Request Body¶
| Field | Type | Required | Description |
|---|---|---|---|
roomId |
string | Yes | The room UUID (matches rooms.room_id and the LiveKit room name). |
zoneId |
string | Yes | Zone identifier within the room. |
Example Request:
{
"roomId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"zoneId": "zone-meeting-room-1"
}
Response (200 OK)¶
{
"code": 200
}
Errors¶
| Code | Reason |
|---|---|
400 |
Invalid request, no active recording found for this room and zone, or user/organization could not be resolved. |
401 |
Missing or invalid authentication token. |
403 |
The active recording belongs to a different organization than the user's current org. |
422 |
Validation error: roomId or zoneId is missing. |
500 |
Internal server error (participant check failure, add participant failure, metadata update failure). |
Behavior¶
- Looks up the active recording (
status = 'active') matching theroomId+zoneIdcombination. - Verifies that the recording belongs to the user's organization.
- Checks if the user is already a participant:
- Already a participant: Returns
200 OKimmediately (idempotent, no database write or metadata update). - New participant: Adds the user to
recording_participantsand updates the room metadata with the new participant list.
Notes¶
- This endpoint is idempotent: calling it multiple times for the same user and recording has no side effects.
- No RBAC permission is required beyond authentication. Any authenticated user in the organization can be added as a participant.
- The room metadata is only updated when a new participant is actually added, avoiding unnecessary LiveKit API calls.