Skip to main content

Video API

Real-time video rooms backed by Orbit Media (Devotel’s hosted SFU; forked from LiveKit OSS under Apache-2 License — see attribution). Two flavors:
  • Scheduled rooms (/api/v1/video/rooms-scheduled) — DB-backed, first-class rooms with a host, schedule, capacity, recording, and per-room guest invites. Use when you want the room to outlive a single session, surface in the inbox as a video conversation, or hand out shareable join links.
  • Ad-hoc rooms (/api/v1/video/rooms) — fire-and-forget Orbit Media rooms created directly against the Orbit Media room service. Use for “click to start a call” flows where the room ends when participants leave.
Base path: /api/v1/video Authentication: API key (X-API-Key) on every authenticated endpoint. The public guest-redeem endpoint (POST /video/invites/:inviteToken/redeem) takes the invite token itself as the credential and requires no API key. Role requirements: mutating endpoints (create, delete, end, recording start/stop, invite create/revoke, participant kick/mute, simulive configure/cancel) require owner, admin, or developer. Read endpoints (list, get) and POST /rooms-scheduled/:id/join are available to any authenticated tenant member; the join route silently downgrades role: "host" to participant for callers that lack the host-mint role. Orbit Media configuration: every endpoint returns 503 SERVICE_UNAVAILABLE if Orbit Media is not configured on the cluster, but the two flavors honour different variables:
  • Ad-hoc rooms (/video/rooms) accept the legacy DEVOTEL_LIVEKIT_URL / DEVOTEL_LIVEKIT_API_KEY as a fallback when DEVOTEL_ORBIT_MEDIA_URL / DEVOTEL_ORBIT_MEDIA_API_KEY are unset, so they stay in service on a cluster that still carries only the legacy vars.
  • Scheduled rooms (/video/rooms-scheduled, including join, end, recording, egress, and participant endpoints) require DEVOTEL_ORBIT_MEDIA_URL and DEVOTEL_ORBIT_MEDIA_API_KEY directly — there is no DEVOTEL_LIVEKIT_* fallback, so a cluster set up with only the legacy vars returns 503 on these endpoints.
Set DEVOTEL_ORBIT_MEDIA_URL and DEVOTEL_ORBIT_MEDIA_API_KEY to keep both flavors in service. The legacy DEVOTEL_LIVEKIT_* vars remain as deprecated ad-hoc-only fallbacks and will be removed in a future release; prefer DEVOTEL_ORBIT_MEDIA_* for all new and migrating deployments. Wire compatibility: Orbit Media speaks the same JWT-signed signaling protocol as the LiveKit OSS fork it descends from. The published @orbit/media-client browser SDK is the supported client. The upstream livekit-client SDK still works at the protocol level today but is not part of Orbit’s supported surface and may diverge.

Scheduled rooms

First-class video rooms backed by tenant_<id>.video_rooms. Each room creates a companion conversations row (channel='video') so the inbox renders it alongside other channels.
Recurring meetings. Pass an optional RFC 5545 RRULE body in recurrence_rule to turn a scheduled room into a recurring series — FREQ=DAILY/WEEKLY/MONTHLY/YEARLY with BYDAY, INTERVAL, COUNT, and UNTIL. When set, the .ics calendar attachment on guest invites carries the matching RRULE line so Gmail, Apple Mail, and Outlook render the full series. Omit the field for a single occurrence. Per-occurrence overrides (EXDATE, single-instance reschedule) and series-update cancel/re-send are not yet exposed.

Schedule a Room

POST /api/v1/video/rooms-scheduled Create a new scheduled video room. Tenant-isolated. Optionally pre-fills a start time, sets a recurrence rule, enables auto-recording, and caps capacity. Returns a host token immediately so the creator can join without a second round-trip.
name
string
required
Human-readable room title (1–200 chars). Surfaces in the inbox and join UIs.
scheduled_at
string
ISO-8601 timestamp with offset (e.g. 2026-05-10T14:00:00Z). Omit or null for “start now”.
recurrence_rule
string
Optional RFC 5545 RRULE body (value only — omit the RRULE: prefix), e.g. FREQ=WEEKLY;BYDAY=MO;COUNT=10. Makes the room a recurring series; the .ics attachment on guest invites carries the matching RRULE line. 8–512 characters. FREQ must be one of DAILY, WEEKLY, MONTHLY, YEARLY; INTERVAL (when present) must be ≥ 1; COUNT (when present) must be ≤ 520; UNTIL (when present) must be YYYYMMDD or YYYYMMDDThhmmssZ. Invalid values are rejected with 400. Omit for a single occurrence.
recording_enabled
boolean
default:"false"
When true, recording starts automatically the moment the first participant joins.
max_participants
integer
Hard cap on concurrent participants (2–300). Orbit Media rejects join attempts past this number. Omit to inherit your plan’s per-tier participant cap — there is no fixed default; an absent value resolves to the tenant’s tier ceiling. Supplying a value above your tier cap is rejected with VIDEO_PARTICIPANTS_CAP_EXCEEDED (400).
settings
object
Arbitrary key-value pairs persisted on the room row. Surfaces back on GET /:id.
Error codes: 503 SERVICE_UNAVAILABLE (Orbit Media not configured), 400 (validation), 403 (caller lacks owner/admin/developer), 500 VIDEO_ROOM_CREATION_FAILED.

List Scheduled Rooms

GET /api/v1/video/rooms-scheduled Retrieve rooms scheduled by the current tenant. Filter by lifecycle status.
status
string
Filter by lifecycle: scheduled, live, ended. Omit for all statuses.
limit
integer
default:"100"
Results to return (1–500).
Error codes: 503 SERVICE_UNAVAILABLE, 400 (invalid query), 500 VIDEO_ROOM_LIST_FAILED.

Get a Scheduled Room

GET /api/v1/video/rooms-scheduled/{id} Fetch a single room plus the live participant roster.
id
string
required
Room UUID returned from POST /rooms-scheduled.
Error codes: 503 SERVICE_UNAVAILABLE, 400 (id not a UUID), 404 NOT_FOUND, 500 VIDEO_ROOM_FETCH_FAILED.

Join a Scheduled Room

POST /api/v1/video/rooms-scheduled/{id}/join Generate a per-participant Orbit Media access token. The resulting JWT is short-lived and scoped to one room + identity.
identity
string
required
Unique participant identity (1–200 chars). Used as the Orbit Media participant id. Reusing the same identity in the same room kicks the prior session.
display_name
string
Friendly name shown to other participants.
role
string
default:"participant"
participant or host. Server-side guard: callers that are not owner / admin / developer are silently downgraded to participant.
user_id
string
Optional UUID of an Orbit user this participant represents. Defaults to the calling user.
contact_id
string
Optional UUID of a contact the participant represents (CRM linkage).
ttl_seconds
integer
Token TTL in seconds (60–86400). Defaults to the service-configured value.
Error codes: 503 SERVICE_UNAVAILABLE, 400 (validation), 404 (room not found), 500 VIDEO_ROOM_JOIN_FAILED.

End a Scheduled Room

POST /api/v1/video/rooms-scheduled/{id}/end Terminate the room immediately. All participants are disconnected and the room transitions to ended. Recording (if active) is stopped and flushed.
Error codes: 503 SERVICE_UNAVAILABLE, 400 (id not a UUID), 403 (caller lacks owner/admin/developer), 404 (room not found), 500 VIDEO_ROOM_END_FAILED.

Start Recording

POST /api/v1/video/rooms-scheduled/{id}/recording/start Begin an Orbit Media egress recording on the room. Returns the egress id to track via the Orbit Media webhook lifecycle. If recording_enabled was set at creation time the recording auto-starts and this call is a no-op.
Error codes: 503 SERVICE_UNAVAILABLE, 403 (caller lacks owner/admin/developer), 404 (room not found), 500 VIDEO_RECORDING_START_FAILED.

Stop Recording

POST /api/v1/video/rooms-scheduled/{id}/recording/stop End the Orbit Media egress. The recording is finalised and the resulting URL (MP4 / HLS depending on egress settings) is written back to the room row asynchronously via the Orbit Media webhook.
Error codes: 503 SERVICE_UNAVAILABLE, 403, 404 (room not found), 500 VIDEO_RECORDING_STOP_FAILED.

Guest invites

Issue tokenised join links to people outside your tenant. The invite token IS the credential — anyone who holds it can redeem it (subject to expires_at + max_uses) without an Orbit account.

Create an Invite

POST /api/v1/video/rooms-scheduled/{id}/invites Mint a guest-redeemable invite for a scheduled room. The returned invite_token is what you embed in a shareable URL.
participant_name
string
Optional name pre-filled for the guest in the redeem flow (1–120 chars).
expires_in_hours
integer
Invite TTL in hours (1–168). Defaults to a service-configured value (typically 24).
max_uses
integer
Cap on the number of redemptions (1–500). Omit or null for unlimited.
Error codes: 503 SERVICE_UNAVAILABLE, 400 (validation), 403, 404 (room not found), 500 VIDEO_INVITE_CREATE_FAILED. Rate-limited per the authenticated-write bucket.

List Invites

GET /api/v1/video/rooms-scheduled/{id}/invites Retrieve every invite issued for a room.
include_expired
boolean
default:"false"
When true, also returns invites past expires_at.
limit
integer
default:"100"
Results to return (1–500).
Error codes: 503 SERVICE_UNAVAILABLE, 400 (invalid query), 404 (room not found), 500 VIDEO_INVITE_LIST_FAILED. Rate-limited per the authenticated-read bucket.

Revoke an Invite

DELETE /api/v1/video/rooms-scheduled/{id}/invites/{inviteId} Permanently revoke an invite. Subsequent redeem attempts return 404.
Error codes: 503 SERVICE_UNAVAILABLE, 400 (id / inviteId not UUIDs), 403, 404 (invite not found), 500 VIDEO_INVITE_REVOKE_FAILED. Rate-limited per the authenticated-write bucket.

Redeem an Invite (public)

POST /api/v1/video/invites/{inviteToken}/redeem No authentication. Guest-facing endpoint. Exchanges an invite token for a short-lived Orbit Media JWT scoped to one room. Rate-limited per IP (30 requests / minute).
inviteToken
string
required
The opaque invite token returned by POST /rooms-scheduled/{id}/invites. 24–64 characters, [A-Za-z0-9_-]+.
display_name
string
required
Friendly name shown to other participants (1–120 chars).
identity
string
Optional URL-safe identity ([A-Za-z0-9_:-]+, 1–120 chars). If omitted, the service mints one.
Error codes: 400 (validation, including malformed token), 404 (invite not found / revoked / expired / max-uses exhausted), 429 (per-IP rate-limit), 500 VIDEO_INVITE_REDEEM_FAILED.

Host admin controls

Force-disconnect or force-mute participants in a live scheduled room.

Kick a Participant

POST /api/v1/video/rooms-scheduled/{id}/participants/{identity}/kick Immediately disconnect a participant from the room. Their Orbit Media session is terminated; they can rejoin only with a fresh token.
identity
string
required
The Orbit Media identity to kick (matches the identity passed at join time).
Error codes: 503 SERVICE_UNAVAILABLE, 400 (id not a UUID), 403, 404 (participant or room not found), 500 VIDEO_PARTICIPANT_KICK_FAILED. Rate-limited per the authenticated-write bucket.

Mute a Participant

POST /api/v1/video/rooms-scheduled/{id}/participants/{identity}/mute Force-mute a participant’s audio or video track. The participant can unmute themselves; this is a host-driven hard mute, not a permanent block.
kind
string
default:"audio"
audio or video. Selects which track to mute.
Error codes: 503 SERVICE_UNAVAILABLE, 400 (validation, including kind not in ), 403, 404 (participant or room not found), 500 VIDEO_PARTICIPANT_MUTE_FAILED. Rate-limited per the authenticated-write bucket.

Simulive broadcast

Schedule a pre-recorded video to play out to an audience as if it were live — “simulive” (a.k.a. premiere / played-as-live), the standard webinar-platform capability behind Zoom Webinars, Livestorm, and ON24 premieres. Point at an already-uploaded recording, set a start time, and every viewer’s player seeks to the SAME wall-clock-synced position of the file — nobody starts from zero on join. This is a facet of an existing scheduled room, not a separate resource: there’s no simulive id — the config lives on the room itself (settings.simulive), and every response is scoped by the room’s id. Role requirements: GET /simulive and GET /simulive/playout are available to any authenticated tenant member with the video:read scope (video:write works as a superset). PUT /simulive and POST /simulive/cancel require owner, admin, or developer plus the video:write scope. These endpoints read/write the room row directly and never call out to Orbit Media, so — unlike the endpoints above — they do not return 503 SERVICE_UNAVAILABLE when Orbit Media is unconfigured.

Get Simulive Config

GET /api/v1/video/rooms-scheduled/{id}/simulive Fetch the room’s simulive configuration together with the live-computed playout (position, broadcast_state). Rate limit: 120 requests/minute per tenant (auth-read bucket).
Error codes: 422 VALIDATION_ERROR (id not a UUID), 404 NOT_FOUND, 500 VIDEO_SIMULIVE_GET_FAILED.

Configure a Simulive Broadcast

PUT /api/v1/video/rooms-scheduled/{id}/simulive Create or update the room’s simulive config. Every field is optional so you can build the config up incrementally (upload the recording, then set the schedule); set status: "scheduled" once source_url, duration_seconds, and scheduled_start_at are all in place — the request is rejected until they are. This is a full merge over the existing config (unset fields keep their current value), not a full replacement.
status
string
draft or scheduled. Moving to scheduled requires source_url, a positive duration_seconds, and scheduled_start_at to already be set (in this request or a prior one) — otherwise the request is rejected and status stays unchanged. Moving to draft or scheduled clears a prior cancellation.
title
string
Optional broadcast title shown to viewers (up to 200 chars). null clears it.
source_url
string
Playable URL (HLS .m3u8 or MP4) of the pre-recorded asset a broadcast player dereferences client-side. Must be http(s). null clears it.
source_recording_id
string
Optional provenance pointer back to the unified recording behind source_url (up to 128 chars). null clears it.
duration_seconds
number
Total length of the source recording, in seconds (greater than 0, up to 86400 — 24 hours). Drives the playhead math — set it to the recording’s actual runtime. null clears it.
scheduled_start_at
string
ISO-8601 wall-clock time the playout begins. null clears it.
loop
boolean
default:"false"
When true, playout restarts from 0 instead of ending once duration_seconds elapses.
chat_enabled
boolean
default:"true"
Whether live chat is layered over the broadcast.
qa_enabled
boolean
default:"true"
Whether live Q&A is layered over the broadcast.
Rate limit: 60 requests/minute per tenant (auth-write bucket).
Error codes: 422 VALIDATION_ERROR (field-level, e.g. id not a UUID, source_url not http(s), duration_seconds out of range), 400 VIDEO_SIMULIVE_INVALID (business rule, e.g. requesting status: "scheduled" while source_url / duration_seconds / scheduled_start_at are still missing — the message lists exactly which), 403 (caller lacks owner/admin/developer or video:write), 404 NOT_FOUND (room not found), 500 VIDEO_SIMULIVE_UPDATE_FAILED. Rate-limited per the authenticated-write bucket.

Cancel a Simulive Broadcast

POST /api/v1/video/rooms-scheduled/{id}/simulive/cancel Cancel a scheduled (or draft) simulive broadcast. Idempotent — canceling an already-canceled broadcast is a no-op that preserves the original canceled_at. To resume broadcasting on the same room, PUT /simulive with status: "draft" or "scheduled", which clears the cancellation. Rate limit: 60 requests/minute per tenant (auth-write bucket).
Error codes: 422 VALIDATION_ERROR (id not a UUID), 403 (caller lacks owner/admin/developer or video:write), 404 NOT_FOUND (room not found), 500 VIDEO_SIMULIVE_CANCEL_FAILED. Rate-limited per the authenticated-write bucket.

Get Simulive Playout

GET /api/v1/video/rooms-scheduled/{id}/simulive/playout The lightweight, poll-friendly playhead a broadcast player hits repeatedly to stay wall-clock-synced with every other viewer — no audit trail, minimal payload (no room_status / config, just room_id and playout).
Polling contract. There is no push/websocket channel for playout — a player polls this endpoint (every 5–15 seconds is a reasonable default) and seeks its <video> element to playout.position_seconds of playout.source_url. Branch on broadcast_state: show a countdown using seconds_until_start while upcoming; play from position_seconds and keep polling while live; stop and show an ended state once ended; hide the player entirely on canceled or not_configured. When loop is true, watch loop_iteration to detect a wraparound instead of treating a drop in position_seconds as a seek backwards.
Rate limit: 120 requests/minute per tenant (auth-read bucket).
Error codes: 422 VALIDATION_ERROR (id not a UUID), 404 NOT_FOUND (room not found), 500 VIDEO_SIMULIVE_PLAYOUT_FAILED.

Ad-hoc rooms

Fire-and-forget Orbit Media rooms. No DB row, no inbox conversation, no recording. Use when the room only needs to live for one session and disappear when participants leave. Room names are tenant-prefixed on the Orbit Media side (invariant #TI-P0-3) so collisions across tenants are impossible.

Create an Ad-hoc Room

POST /api/v1/video/rooms Create an Orbit Media room and receive a host token in the same response.
name
string
required
URL-safe room name (3–200 chars, [a-zA-Z0-9_-]+). Tenant prefix is prepended internally; display_name echoes the un-prefixed name.
max_participants
integer
Hard cap on concurrent participants (2–300).
metadata
object
Arbitrary key-value pairs forwarded to Orbit Media.
Error codes: 503 SERVICE_UNAVAILABLE, 400 (validation, including non-URL-safe name), 403, 500 VIDEO_ROOM_CREATION_FAILED.

List Ad-hoc Rooms

GET /api/v1/video/rooms List active Orbit Media rooms owned by the current tenant. Cursor-paginated by room name.
cursor
string
Room name to resume from. Returned in the previous page’s pagination.next_cursor.
limit
integer
default:"25"
Results per page (max 200).
Error codes: 503 SERVICE_UNAVAILABLE, 400 INVALID_CURSOR, 500 VIDEO_ROOM_LIST_FAILED.

Get an Ad-hoc Room

GET /api/v1/video/rooms/{name} Fetch a specific ad-hoc room with its live participant roster. Pass either the prefixed wire name or the bare display name.
name
string
required
Room name (with or without the tenant_<id>_ prefix).
Error codes: 503 SERVICE_UNAVAILABLE, 404 NOT_FOUND, 500 VIDEO_ROOM_DETAILS_FAILED.

Delete an Ad-hoc Room

DELETE /api/v1/video/rooms/{name} Close the Orbit Media room and disconnect every participant.
Error codes: 503 SERVICE_UNAVAILABLE, 403 (caller lacks owner/admin/developer), 500 VIDEO_ROOM_CLOSE_FAILED.

Issue a Participant Token

POST /api/v1/video/rooms/{name}/token Generate a per-participant Orbit Media join token for an ad-hoc room. Use this when the host needs to invite someone else into the room they created.
participant_name
string
required
Friendly participant name (1–200 chars). Also used as the Orbit Media identity.
metadata
object
Arbitrary key-value pairs attached to the Orbit Media participant.
Error codes: 503 SERVICE_UNAVAILABLE, 400 (validation), 403, 500 TOKEN_GENERATION_FAILED.

Room templates

Save a room’s setup once — participant cap, recording and egress defaults, media policy, E2EE, region, and the waiting-room lobby — as a named, reusable room template (“meeting template”), then spin up consistently-configured rooms from it. Templates are tenant-scoped and stored as a non-secret config preset; recording-storage credentials and consent receipts are never persisted on a template. Reads (list, get) require the video:read scope (video:write works as a superset). Writes (create, update, delete, instantiate) require the owner, admin, or developer role plus the video:write scope. Each tenant may keep up to 100 templates. The template config is a bag of optional knobs that map 1:1 to the identically-named Schedule a Room fields: max_participants, max_duration_minutes, recording_enabled, recording_format, recording_output, simulcast, spatial_layers, max_bitrate_kbps, video_codec, svc_scalability_mode, e2ee, e2ee_key_provider, virtual_background, noise_suppression, spatial_audio, audio_only, captions_required, region, waiting_room, and waiting_room_auto_promote. An empty config ({}) is a valid “all defaults” template. Unknown keys are rejected, and svc_scalability_mode is only valid with a vp9 or av1 video_codec.

List Room Templates

GET /api/v1/video/room-templates Returns the tenant’s saved templates. Requires the video:read scope. Rate limit: 120 requests/minute per tenant (auth-read bucket).

Get a Room Template

GET /api/v1/video/room-templates/{id} Fetch a single template by its vrtpl_-prefixed id. Requires the video:read scope. A missing id returns 404 NOT_FOUND.

Save a Room Template

POST /api/v1/video/room-templates Create a new template. Owner/admin/developer only. Names must be unique within the tenant (case-insensitive).
name
string
required
Label shown in the template picker (1–120 characters). Unique within the tenant (case-insensitive).
description
string
Optional operator description (up to 500 characters).
config
object
required
The reusable, non-secret room-config preset (see the knob list above). May be an empty object for an “all defaults” template.
Rate limit: 60 requests/minute per tenant (auth-write bucket).
Error codes: 400 VIDEO_ROOM_TEMPLATE_INVALID (duplicate name or template cap reached), 422 VALIDATION_ERROR, 403, 500 VIDEO_ROOM_TEMPLATE_CREATE_FAILED.

Update a Room Template

PATCH /api/v1/video/room-templates/{id} Partial update. Supply at least one of name, description, or config; description: null clears the description. config is a full replacement of the stored preset (validated in whole), not a deep-merge — send the complete config you want, not just the changed knobs. Owner/admin/developer only.
Error codes: 400 (invalid field or name collision), 404 NOT_FOUND, 422 VALIDATION_ERROR (no updatable field supplied), 403, 500 VIDEO_ROOM_TEMPLATE_UPDATE_FAILED.

Delete a Room Template

DELETE /api/v1/video/room-templates/{id} Remove a template. A missing id returns 404 NOT_FOUND, so a delete never silently no-ops a mistyped id. Owner/admin/developer only.

Create a Room from a Template

POST /api/v1/video/room-templates/{id}/instantiate Create a live (or scheduled) room from a saved template. The template’s stored config is applied to the room-create path with the same per-tier participant-cap gate, required-captions guard, media/E2EE/region knobs, waiting-room lobby, recording-consent gate and auto-start, inbox backlink, and audit trail as Schedule a Room. Owner/admin/developer only.
name
string
required
Label for the new room (1–200 characters). Per-instance — it does not change the template.
scheduled_at
string
Optional ISO 8601 date-time to schedule the room for. Omit or send null to create it live now.
Consent-receipt id threaded into the recording-consent gate when the template auto-starts a recording in a two-party-consent jurisdiction.
Rate limit: 60 requests/minute per tenant (auth-write bucket).
Error codes: 400 VIDEO_PARTICIPANTS_CAP_EXCEEDED (template max_participants above your plan cap), 404 NOT_FOUND, 422 VALIDATION_ERROR / RECORDING_CONSENT_REQUIRED, 503 SERVICE_UNAVAILABLE (video service not configured) or 503 VIDEO_CAPTIONS_NOT_CONFIGURED (template requires live captions the deployment lacks), 500 VIDEO_ROOM_TEMPLATE_INSTANTIATE_FAILED.

Room usage analytics

Operator-facing aggregate of how your organization is using video rooms: sessions over time, average and peak concurrent participants, total room-minutes, recording success rate, and join failures. Read-only, tenant-scoped. This is the room-level usage report — distinct from per-recording chapter themes and per-call connection quality.

Get Room Usage

GET /api/v1/video/rooms-analytics/usage Returns a window summary plus a daily time series aggregated from realized room sessions. One bucket per UTC day. Requires the video:read scope (video:write works as a superset).
from
string
Start of the window, ISO 8601 (e.g. 2026-06-01T00:00:00Z). Defaults to 30 days before to.
to
string
End of the window, ISO 8601. Defaults to now. Must be later than from or the request returns 400 INVALID_WINDOW.
The daily series is capped at 366 buckets (a full leap year). A longer window still returns a summary covering the entire range, but the daily array is truncated to the first 366 days. The summary window anchors on each session’s start time, falling back to its creation time, so rooms that were created but never connected still appear in the totals as join failures.
Rate limit: 120 requests/minute per tenant (auth-read bucket).
Response fields
  • window — the resolved from/to (ISO 8601) after defaults are applied.
  • summary.total_sessions — realized room sessions in the window.
  • summary.total_room_seconds / total_room_minutes — total room wall-clock across all sessions.
  • summary.avg_peak_participants — average of each session’s peak concurrent participants (sessions that never connected are excluded so empty rooms don’t drag it down).
  • summary.max_peak_participants — busiest single moment in the window.
  • summary.total_join_events — count of participant join events (a rejoin counts twice).
  • summary.recordings_started / recordings_succeeded — recordings attempted vs landed.
  • summary.recording_success_raterecordings_succeeded / recordings_started, 01, or null when no recording was attempted.
  • summary.join_failures — sessions where the room was created but no participant ever connected.
  • summary.error_sessions — sessions that terminated with an error.
  • daily[] — one bucket per UTC day with date (YYYY-MM-DD), sessions, room_minutes, avg_peak_participants, and max_peak_participants.
Error codes: 400 VALIDATION_ERROR (malformed from/to), 400 INVALID_WINDOW (from later than to), 403 (missing video:read scope), 500 VIDEO_ROOM_USAGE_ANALYTICS_FAILED.

Inbound webhook

Orbit Media posts room and recording lifecycle events to POST /api/v1/video/webhook. The endpoint is platform-managed — you don’t call it directly. Events received here drive started_at / ended_at / recording_url updates on video_rooms and the matching inbox-conversation status transitions. Orbit Media signs each delivery with a JWT minted from the API secret; Orbit validates the signature via the SFU’s WebhookReceiver (carried over from the LiveKit OSS fork) before processing.

See also