Skip to 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

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}.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

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_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

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

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

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

  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:
  4. Already a participant: Returns 200 OK immediately (idempotent, no database write or metadata update).
  5. 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.