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 anerror.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;
quality symptoms after a successful join live on
Troubleshooting: video room and 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:- Room id and, when you have it, the
room_sidfrom the join response. - Error code verbatim —
error.codefrom the response body. - Feature endpoint and request body — e.g.
POST /:id/polls/votewithpoll_id. request_id— themeta.request_idon the response envelope.- Timestamp in UTC of the refusal.
See also
- Troubleshooting: video room lifecycle failures — mint, capacity, lobby, dispatch-agent, and the egress body contract that pairs with the RTMP section above.
- Troubleshooting: video room and call quality — the quality sibling for media after the features work.
- Video channel: rooms, embeds, recording, and broadcast — the feature surface this page diagnoses.
- Error codes reference — the complete
VIDEO_*code family.