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

# Video room features: chat, polls, Q&A, invites, RTMP

> Diagnose the in-room feature refusals — chat send/policy blocks, poll and Q&A failures, guest-invite lifecycle, moderation and lock errors, hands/spotlight/whiteboard, RTMP/livestream playback, and the SSO-policy gate.

# Video room features: chat, polls, Q\&A, invites, RTMP

Once everyone is in and media is healthy, failures move from the
lifecycle (mint, capacity, lobby) to the in-room features: a chat
message that will not send or gets blocked by the content policy, a
poll that refuses to launch, an invite that a guest cannot redeem, a
lock or ban the host cannot apply, a whiteboard stroke that desyncs, or
an RTMP push that starts but the playback URL never resolves. Every one
of those is a refusal with an `error.code` on the API response — match
the code to the feature section below and work it.

Lifecycle refusals (join token, capacity, lobby, dispatch) live on
[Troubleshooting: video room lifecycle failures](/troubleshooting/video-room-lifecycle);
quality symptoms after a successful join live on
[Troubleshooting: video room and call quality](/troubleshooting/video-call-quality).
This page covers the feature-level `VIDEO_*` codes those pages point
past.

Most feature codes are safe to retry after the config hint is fixed —
each feature runs as a server-side broadcast or audit-backed write, so
a retry never double-applies. The gateway codes
(`VIDEO_CHAT_BLOCKED_BY_POLICY`, `VIDEO_INVITE_REDEEM_FAILED`,
`VIDEO_ROOM_LOCKED`) are the exception: a retry with the same content
or token refuses the same way.

## Chat

The in-room text chat routes through a per-room content policy before
the broadcast, so most chat failures are policy verdicts, not
transients.

### `VIDEO_CHAT_SEND_FAILED`

The broadcast to the room's data channel failed after the policy screen
passed. Transient — retry with backoff; the envelope carries the
client-supplied `message_id`, so a retry does not duplicate the message
in replay history.

### `VIDEO_CHAT_BLOCKED_BY_POLICY` (422)

The room's content policy rejected the message — it is never broadcast,
never persisted, and never enters the replay history or recording. The
response carries `error.details.categories` naming the policy category
that matched (or a blocklist hit). Do not retry the same text: either
edit the message content, or adjust the room policy on
`PUT /:id/chat/policy` (`block` / `flag` / `log` modes) if the category
is configured too strictly. `flag` and `log` modes deliver the message;
only `block` refuses.

### `VIDEO_CHAT_POLICY_UNAVAILABLE` (503)

The policy read failed, and the route fails closed rather than deliver
unchecked content. Retry once; if it repeats across a few minutes, the
policy evaluation layer is degraded — escalate with the `request_id`.

### `VIDEO_CHAT_DELETE_FAILED`

Host moderation — removing a single message from the shared history —
failed. Retry once after fetching fresh chat; another host may have
already removed it.

### `VIDEO_CHAT_CLEAR_FAILED`

Host cleared the entire deck failed. Same handling — check fresh state
before a second clear so you do not clear messages added between the
two attempts.

## Polls & Q\&A

Polls and moderated Q\&A run as server-side broadcast envelopes over the
data channel, so failures are almost always transient data-plane
refusals.

### `VIDEO_POLL_LAUNCH_FAILED`

Only the room's host, panelist, or moderator session can launch — a
viewer role earns a 403 from the write guard before this code ever
fires. The launch itself failed: retry with backoff; the `poll_id` you
choose is client-generated, so a retry replaces the same poll, never
duplicates it.

### `VIDEO_POLL_VOTE_FAILED`

The vote write failed. The `voter_identity` is server-stamped (a client
cannot vote as someone else), so a retry is safe — retry with the same
`poll_id` and `option_ids`.

### `VIDEO_POLL_CLOSE_FAILED`

Host-side close failed. Fetch the current poll state first; a close
that partially applied is treated as open for a retry.

### `VIDEO_QA_ASK_FAILED`

Question submission failed. Retry with the same `question_id` —
client-chosen, so no duplicate question lands on a retry.

### `VIDEO_QA_ANSWER_FAILED`

Host answer post failed. Retry once; check fresh Q\&A state first in
case the answer landed and the response dropped.

## Invites

Guest-invite tokens are minted per room by the host, then a guest
redeems the link. Most invite failures are lifecycle stages of the
token, not transients.

### `VIDEO_INVITE_CREATE_FAILED`

Token mint failed downstream of validation. Retry with backoff —
`expires_in_hours` (1–168 hours, default bounded) and `max_uses`
(1–500) bound the token's lifetime, so a retry only issues another
bounded token.

### `VIDEO_INVITE_REDEEM_FAILED`

The guest clicked `/video/invites/:inviteToken/redeem` and the token
either expired, exhausted `max_uses`, or was revoked. The refusal
message says the link may have expired, but any of those three apply.
The fix is never a retry: issue a fresh invite and hand the new link to
the guest. On a multi-use invite, check `GET /:id/invites` for the
token's remaining uses.

### `VIDEO_INVITE_REVOKE_FAILED`

Host revoke failed. List the invites first — a stale `invite_id` or a
racing revoke by another host already removed it. Retry only after a
fresh list.

### `VIDEO_INVITE_LIST_FAILED`

Listing the room's invites failed while computing the active-token
field. Safe to retry — read-only; if the 500 repeats across minutes,
escalate with `request_id`.

## Moderation

Moderation actions are host/panelist-only writes, split across the
participant-ban list, role/tier handoff, and the room lock.

### `VIDEO_BAN_LIST_FAILED`

Loading the persisted banned-participants list (`GET /:id/bans`)
failed. Read-only; retry.

### `VIDEO_PARTICIPANT_BAN_FAILED`

Ban write failed (`POST /:id/participants/:identity/ban`). Retry after
fetching fresh room state — the ban row is idempotent on the identity,
so a retry that lands after a racing unban simply re-bans.

### `VIDEO_PARTICIPANT_UNBAN_FAILED`

Ban-lift failed (`DELETE /:id/participants/:identity/ban`). Check the
ban list first; a missing row means another host already lifted it.

### `VIDEO_PARTICIPANT_ROLE_UPDATE_FAILED`

Role handoff (`host` / `panelist` / `viewer`) failed. Only an
owner/admin session can re-tier into `host`; the route refuses below
that with a 403 before this 500 fires. Retry after re-fetching the
participant's current role.

### `VIDEO_PARTICIPANT_TIER_UPDATE_FAILED`

Publish/receive tier update failed (`POST
/:id/participants/:identity/tier`). The tier names map onto the
publish/receive grant; retry after re-fetching the participant.

### `VIDEO_PARTICIPANT_RECEIVE_CONTROL_FAILED`

Per-subscriber receive-settings update failed (`POST
/:id/participants/:identity/receive-control`). Transient — retry.

### `VIDEO_ROOM_LOCKED` (409)

`POST /:id/lock` claims the room is currently locked so the action the
caller attempted (often a join, or a lock-opposite-precondition write)
was refused. Read the room state first — a locked room refuses joins by
design. Unlock, then retry the original action.

### `VIDEO_ROOM_LOCK_FAILED`

Lock/unlock toggle failed mid-transition. Fetch the current lock state
before a second toggle so you know which direction to flip.

## Hands, spotlight, whiteboard

These are the in-room engagement + moderator-pin surfaces — all
broadcast envelopes over the data channel, so the failure shape matches
polls.

### `VIDEO_HAND_RAISE_FAILED`

Raise-hand failed (`POST /:id/hand-raise`). Only owners and admins can
raise on behalf of another identity below this update — a role-gate 403
fires before the 500. Retry.

### `VIDEO_HAND_LOWER_FAILED`

Lower-hand or queue-clear failed (`POST /:id/hand-lower`, or host
`POST /:id/hand-queue/clear`). Fetch the raised-hand queue (`GET
/:id/hand-queue`) first, then lower only identities still on it.

### `VIDEO_SPOTLIGHT_SET_FAILED`

Spot/pin a participant failed (`POST /:id/spotlight`). Only one
spotlight is live at a time — a second set clears the first — so retry
after checking which participant is pinned.

### `VIDEO_WHITEBOARD_OPEN_FAILED` / `VIDEO_WHITEBOARD_DRAW_FAILED` / `VIDEO_WHITEBOARD_CLOSE_FAILED` / `VIDEO_WHITEBOARD_CLEAR_FAILED`

Whiteboard lifecycle (`/open`, `/draw`, `/close`, `/clear`). The
whiteboard is a shared broadcast surface; the open sets the scene, one
stroke per draw envelope, and close/clear tear it down. If
`DRAW_FAILED` repeats while `open` was clean, the room may have rotated
its channel — re-open before retrying the stroke. `CLEAR_FAILED` and
`CLOSE_FAILED` both route the host, not a guest — a 403 from the guard
precedes them.

## Livestream & RTMP

RTMP egress and HLS/WHEP playback run through the egress control plane.

### `VIDEO_RTMP_EGRESS_START_FAILED`

The composite render start refused (`POST /:id/egress/rtmp`). Validate
the body first — the most common cause is a malformed destination (the
`rtmp://` scheme on `rtmp_url`, `stream_key` as the separate field).
The control plane echoes URLs without keys and never persists the
stream key, so a retry that fails the same way will not leak it. When
the body is clean, retry once before escalating.

### `VIDEO_RTMP_EGRESS_STOP_FAILED`

`DELETE /:id/egress/rtmp/:egressId` is idempotent — retry the delete; a
leaked egress keeps rendering an empty room to a live destination, so
stop it even if the room endocard finished. If the stop persists
through two retries, escalate with `egress_id`.

### `VIDEO_LIVESTREAM_PLAYBACK_FAILED`

The playback URL for the fan-out (`GET /:id/livestream`, or the DVR
timeshift route) failed to resolve after egress start. Retry once; if
it repeatedly refuses, check whether the egress is still running — a
stopped egress cannot mint a playback URL.

## SSO policy

Rooms can clamp access to your enterprise IdP with a per-room SSO
required toggle.

### `VIDEO_ROOM_SSO_POLICY_FAILED`

Both the `GET` and `POST /:id/sso-policy` surfaces carry this code
because the same policy evaluation runs on both paths. The refusal is
not a misconfiguration message — it says the policy read or update
failed. Key behavior: the room derives `can_arm` on the `GET` by
evaluating the ARMED candidate policy, so a session that is not
currently SSO-federated reports `can_arm: false` — and the toggle stays
clickable into the guaranteed 409 below.

`VIDEO_ROOM_SSO_POLICY_FAILED` on `POST` almost always follows the
self-lockout guard: a session that would be locked out by enabling
`required: true` refuses the update. Federate your session through SSO
or drop the requirement before posting; the `GET` above tells you
whether the current session could survive the armed policy.

## When to escalate

Work the feature section first — most codes resolve on the first
config fix or one retry. When the same code survives about ten minutes
of retries, open a ticket with:

1. **Room id** and, when you have it, the **`room_sid`** from the join
   response.
2. **Error code verbatim** — `error.code` from the response body.
3. **Feature endpoint and request body** — e.g.
   `POST /:id/polls/vote` with `poll_id`.
4. **`request_id`** — the `meta.request_id` on the response envelope.
5. **Timestamp in UTC** of the refusal.

## See also

* [Troubleshooting: video room lifecycle failures](/troubleshooting/video-room-lifecycle)
  — mint, capacity, lobby, dispatch-agent, and the egress body contract
  that pairs with the RTMP section above.
* [Troubleshooting: video room and call quality](/troubleshooting/video-call-quality)
  — the quality sibling for media after the features work.
* [Video channel: rooms, embeds, recording, and broadcast](/channels/video)
  — the feature surface this page diagnoses.
* [Error codes reference](/reference/error-codes) — the complete
  `VIDEO_*` code family.
