> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# The video room model

> How Orbit structures video rooms — scheduled versus ad-hoc creation, the created-to-closed lifecycle, join-token semantics, recording and broadcast egress, the analytics split, live interpretation and captions, and QA scoring

# The video room model

Video is a first-class Orbit channel: a meeting joins an inbox conversation,
triggers the same recording lifecycle as a call, and feeds the same analytics
and QA stack as voice. This page explains the room model itself — how rooms
are created, how they live and die, how participants get in, and where the
observability surfaces hang off it. The endpoint-by-endpoint reference lives
in the [Video API reference](/api-reference/video); this page is the model
those endpoints implement.

## Scheduled versus ad-hoc rooms

A room is created **scheduled** or **ad-hoc**. The difference is a timed
access window versus an open one.

|               | Scheduled room                                                                                  | Ad-hoc room                                                                                   |
| ------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| Created by    | `POST /video/rooms-scheduled` with a `scheduled_at` timestamp, or instantiating a room template | `POST /video/rooms-scheduled` with no `scheduled_at`, or a template instantiated with no time |
| Access window | Join opens at the scheduled time; participants hold an intent before the first session          | Join is open immediately                                                                      |
| Room kind     | A persisted intent row (template / calendar slot) that can run repeatedly                       | No intent row; the session itself is the record                                               |
| Best for      | Interviews, consult booking, recurring weekly stand-ups, webinars with registrant check-in      | Support escalations, "start now" button clicks, ad-hoc collaboration                          |

The split is structural, not cosmetic: a scheduled room carries an intent
record that can recur across many sessions (a recurring meeting re-uses the
same room row each time it convenes), while an ad-hoc room is the session
itself. History lives in `video_room_sessions` — one row per realised room
lifetime, scheduled or ad-hoc — so the "Recent rooms" panel and the
retention sweep see both uniformly.

<Note>
  Room templates are the middle case: a saved configuration you instantiate
  either live or scheduled. A template has no session history of its own until
  it is instantiated.
</Note>

## Lifecycle: created → active → closed

A scheduled room moves a status workflow: **pending → ready → live →
ended**. Ad-hoc rooms are `live` as soon as the first participant joins.

* **created** — the room row exists, and the intent (if scheduled) is
  persisted. No media is running yet.
* **active (live)** — at least one participant is in the room. For
  scheduled rooms the create-step auto-recording hook deliberately skips
  "future" rooms and arms on first join instead, because the media server
  reaps unjoined scheduled rooms.
* **closed (ended)** — the room is closed when the host ends it, when the
  empty-room reap fires, or when a broadcast egress is stopped. The
  `room_finished` webhook persists the final state (max participants, join
  events, totals, recording IDs) into `video_room_sessions`. Closing is a
  host action, not a participant one.

A scheduled room can be reopened: reconvening a recurring meeting appends a
new `video_room_sessions` row rather than mutating the intent. Retention is
per-workspace and sweeps soft-then-hard-delete session rows after the
retention window — the same scheduler that handles call recordings.

## Join tokens

Joining a room returns an **access token** — a short-lived JWT scoped to one
room and one identity. It is an SDK credential, passed to
`Room.connect(url, token)`; it is never an HTTP `Authorization` header.

Two tiers matter in the response:

* **host token** — the room creator's join token (minted by the create
  endpoint as `host_token`).
* **participant token** — minted by `POST /video/rooms-scheduled/{id}/join`
  for a named identity, or purchased by a guest who redeems an invite link
  via `POST /video/invites/{inviteToken}/redeem`.

Tokens expire (`expires_in_seconds` + an absolute `expires_at`); re-mint a
fresh token for the same identity before expiry to avoid dropping
mid-session. The response also carries the **effective grant** — a
`participant_tier` (`viewer`, `panelist`, `host`, `hidden_supervisor`) plus
`permissions` after any host downgrade or waiting-room clamp — which your
client must respect before showing publish controls. The full flow, the
exact connection call, and the guest-invite alternative live in
[Video room access tokens](/guides/video-room-access-tokens).

## Recording and broadcast

Recording is opt-in per room (`recording_enabled` on the create body).
Auto-start is deferred for scheduled-future rooms, and the egress handle is
recorded on the session row when the room closes. Composite (one mixed room
render) and per-participant track egress are separate paths —
per-participant egresses are a fan-out, so they persist on the room's
settings rather than the session's single recording columns.

Broadcast egress is a separate discipline from in-room participation:

* **RTMP/HLS egress** (`POST /:id/egress/rtmp`, `POST /:id/egress/hls`)
  pushes the live stream to a CDN or an external endpoint.
* **Broadcast viewers consume over HLS/WHEP through a CDN** — they are
  **not** in-room participants. Their audience signal is each viewer
  player's own heartbeat (`POST /:name/heartbeat` on `/video/broadcast-viewership`),
  maintaining a sliding-window concurrent-viewer count in Redis.
* **Registrants** are the people who signed up before the room went live —
  check-in and approval/decision flows live on `/video/rooms-scheduled/{id}/registrants`.
  A registrant becomes an in-room participant only at join; a viewer may
  never join at all.

The [Recording lifecycle](/concepts/recording-lifecycle) page covers the
egress lifecycle webhook family; the broadcast-viewership read path exists
precisely because every other video analytic measures in-room participants
and none counted the broadcast audience.

## Analytics model

Three aggregates split video analytics by scope. Knowing which one to read
avoids a cross-window fan-out:

* **Room usage** (`/video/room-usage-analytics`) — org-level usage over
  time: sessions, peak/average concurrent participants, room-minutes,
  recording success rate, join failures. Answers "how is my org using
  video." This aggregates `video_room_sessions`.
* **Engagement** (`POST/GET /rooms/:name/sessions/:sessionId/engagement`) —
  per-session, per-participant: talk-time share, screen-share duration,
  hand-raises, merged dominant-speaker timeline. The host browser buffers
  active-speaker / screen-share / hand-raise signals during the meeting and
  flushes them at room-end — which keeps the hot \~10 Hz SFU
  `speakers_changed` webhook path write-free.
* **Chapter analytics** (`/video/chapter-analytics`) — cross-recording
  theme aggregation: "Pricing objection", "Demo walk-through" —
  per-theme average duration across a date window. Read-only; chapter
  rows are written by the chaptering pipeline on `recordings.chapters`.

Use room-usage for capacity and trend, engagement for comparing
participants inside a session, and chapter analytics for recurring-theme
analysis across recordings.

## Live interpretation and captions

Live interpretation is the **spoken interpreter channel** — the audio analog
of the translated caption line. A participant's client posts one finalized
caption utterance plus a target language to `/video/live-interpretation`,
and the platform returns the spoken audio; `captions/translate` and this
route both derive from the caption/STT pipeline, but one renders on-screen
text while the other renders speech. Captions themselves are buffered by the
host client during the meeting and flushed at room-end along with engagement
— the same buffer-then-flush shape — so a post-meeting transcript and a
post-meeting summary can be built without touching the hot SFU webhook.

## Video QA scoring

QA scoring mirrors the voice coaching pipeline for the video pillar. The
flow, on request for a session:

1. Cache check on `video_room_sessions.metadata.qa_score` — a cached
   scorecard is returned unless `regenerate` is set.
2. Resolve the session row (tenant-scoped; soft-deleted rows return
   `NOT_FOUND`).
3. Resolve the transcript — `transcript_override` → metadata caption
   frames → persisted `video_room_transcripts` (same fall-through the
   post-meeting summary uses).
4. Compute **talk-balance** deterministically from
   `video_participant_engagement` (no LLM): per-participant talk share plus
   a normalised-entropy balance score.
5. Run an LLM judge over the **redacted** transcript (PII stripped first,
   mirroring voice QM scoring) to score compliance and per-dimension
   quality, and emit coaching tips.
6. Persist the merged scorecard back into `metadata.qa_score` and return.

A pre-feature tenant degrades to `has_data:false` on the engagement read,
never a 500, and the QA step itself never initiates outbound signalling —
reporting only.

## Diagram: scheduled versus ad-hoc

```text theme={null}
 Scheduled room                          Ad-hoc room
 ─────────────────────────────────────────────────────────────────
 posted at create with scheduled_at     posted with no scheduled_at
   → status = pending (intent row)        → live on first join
   → first join → status = live           (no intent row; session is
   → empty-reap / host end → ended         the record)
 history: one sessions row per realised   history: sessions row appended
 room lifetime; reunions append           per realised lifetime
 ─────────────────────────────────────────────────────────────────
 then broadcast (optional): RTMP/HLS egress out; viewers are not
 participants
```

## Related

<CardGroup cols={2}>
  <Card title="Video — channel overview" href="/channels/video">
    The channel-level page: what video meetings, webinars, and broadcasts do.
  </Card>

  <Card title="Video meetings — guide" href="/guides/video-meetings">
    How to schedule, join, invite guests, and run a meeting end to end.
  </Card>

  <Card title="Embed a video-consultation button" href="/guides/embed-video-consultation-button">
    Put a "video call us" button on your own site — the embed consumes the
    same join-token flow.
  </Card>

  <Card title="Recording lifecycle" href="/concepts/recording-lifecycle">
    The egress lifecycle webhook family recordings land in.
  </Card>

  <Card title="Room access tokens" href="/guides/video-room-access-tokens">
    Full token scope, expiry, and guest-invite semantics.
  </Card>

  <Card title="Video API reference" href="/api-reference/video">
    The endpoint-by-endpoint contract this model is implemented by.
  </Card>
</CardGroup>
