Skip to main content

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

FieldTypeRequiredDescription
roomNamestringYesThe LiveKit room name (room UUID from the rooms table).
conferenceTopicstringYesDisplay name of the conference/meeting being recorded.
zoneIdstringYesZone identifier within the room where the recording takes place.
participantsstringYesComma-separated display names of participants (used for the recording template URL).
participantIdsstring[]YesWorkOS 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

CodeReason
400Invalid or malformed JSON request body.
401Missing or invalid authentication token.
402Organization's monthly recording quota has been exceeded.
403User does not have the StartRecording permission (RBAC).
409A recording is already active for this room and zone.
422Validation error: one or more required fields are missing or invalid.
500Internal server error (egress start failure, database error, metadata update failure).

Notes

  • The recording file is stored at /{workos_org_id}/{roomName}/{timestamp}.mp4 on 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

FieldTypeRequiredDescription
egressIdstringYesThe egress ID returned from the start recording call.
zoneIdstringYesZone identifier where the recording is active.
roomNamestringYesThe 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

CodeReason
400Invalid or malformed JSON request body.
401Missing or invalid authentication token.
403User does not have the StopRecording permission (RBAC).
409No active recording found for this room and zone.
422Validation error: one or more required fields are missing or invalid.
500Internal server error (egress stop failure, metadata update failure).

Notes

  • Stopping the egress triggers a LiveKit egress_ended webhook, which asynchronously updates the recording row with status, ended_at, duration_seconds, and expires_at.
  • The expires_at is calculated as ended_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

FieldTypeRequiredValidationDescription
limitintYesmin=1, max=100Number of recordings to return.
offsetintYesmin=0Number 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

FieldTypeDescription
idint64Internal recording ID.
egressIdstringLiveKit egress identifier.
roomIdstring (UUID)Room UUID from the rooms table (also used as the LiveKit room name).
conferenceTopicstringDisplay name of the conference.
zoneIdstringZone identifier within the room.
initiatedBystringWorkOS user ID of the user who started the recording.
durationSecondsint | nullRecording duration in seconds. null if still active.
statusstringOne of: active, completed, failed.
startedAtstring (ISO 8601)When the recording started.
endedAtstring | nullWhen the recording ended. null if still active.
expiresAtstring | nullWhen the recording file expires and becomes unavailable for download.
participantsobject[]List of participants with their workosUserId. The frontend resolves display names and avatars from its local cache.

Errors

CodeReason
400Invalid or malformed JSON request body, or organization could not be resolved.
401Missing or invalid authentication token.
422Validation error: limit or offset out of range.
500Internal server error (database query failure).

Access Control

RoleVisibility
AdminAll recordings in the organization.
ModeratorAll recordings in the organization.
MemberOnly 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 their expires_at has passed.
  • Results are ordered by started_at DESC (most recent first).

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

FieldTypeRequiredDescription
recordingIdint64YesThe 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

FieldTypeDescription
urlstringPresigned S3-compatible GET URL for the recording file. Valid until expiresAt.
expiresAtstring (ISO 8601)When the presigned URL (and the recording file itself) expires.

Errors

CodeReason
400Invalid request, recording not found in this organization, or recording status is not completed (still active or failed).
401Missing or invalid authentication token.
403User is not a participant in this recording (Members only; Admin/Moderator bypass this check).
410Recording has expired (expires_at is in the past). The file is no longer available for download.
422Validation error: recordingId is missing or invalid.
500Internal server error (presigned URL generation failure, database error).

Access Control

RoleAccess
AdminAny recording in the organization.
ModeratorAny recording in the organization.
MemberOnly 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 no expires_at is set, the TTL defaults to RECORDING_RETENTION_HOURS.
  • A recording must have status = "completed" to be downloadable. Active or failed recordings return 400.
  • Once expires_at has passed, the endpoint returns 410 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

FieldTypeRequiredDescription
roomIdstringYesThe room UUID (matches rooms.room_id and the LiveKit room name).
zoneIdstringYesZone identifier within the room.

Example Request:

{
"roomId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"zoneId": "zone-meeting-room-1"
}

Response (200 OK)

{
"code": 200
}

Errors

CodeReason
400Invalid request, no active recording found for this room and zone, or user/organization could not be resolved.
401Missing or invalid authentication token.
403The active recording belongs to a different organization than the user's current org.
422Validation error: roomId or zoneId is missing.
500Internal server error (participant check failure, add participant failure, metadata update failure).

Behavior

  1. Looks up the active recording (status = 'active') matching the roomId + zoneId combination.
  2. Verifies that the recording belongs to the user's organization.
  3. Checks if the user is already a participant:
    • Already a participant: Returns 200 OK immediately (idempotent, no database write or metadata update).
    • New participant: Adds the user to recording_participants and 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.