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

# Troubleshooting: video waiting-room and lobby errors — VIDEO_CHAT_LOBBY_HELD, VIDEO_WAITING_ROOM_DENY_FAILED, and the guest refusal path

> Decode every refusal on the video waiting-room path: what a held guest sees, what a failed host admit or deny means, which token clears which 403, and when a 503 is simply a retry.

# Troubleshooting: video waiting-room and lobby errors — VIDEO\_CHAT\_LOBBY\_HELD, VIDEO\_WAITING\_ROOM\_DENY\_FAILED, and the guest refusal path

The waiting room holds every guest outside the meeting until the host admits
them. While a guest is held, their chat sends are refused — that is the lobby
working as configured, not an outage. This page decodes every code on that
path, from the guest's 403 to the host's failed deny, and closes with the
evidence bundle support needs when a refusal outlives its fix.

## What these are — guests, the waiting room, and who an error is about

A **guest** is a participant with no Orbit account session: they opened an
invite join link, landed on the guest-join page, and received a LiveKit
session for the room. The [Waiting-room hold (chat)](/reference/glossary)
glossary entry and the
[video guest chat refusals](/troubleshooting/video-chat-guest-forbidden)
runbook cover the chat-send side in depth; this page covers the
waiting-room path itself, on both sides of the lobby door.

The **waiting room** is a per-room host control. When it is armed, guests
connect into a lobby tier instead of the meeting, and the host admits or
denies each pending guest from the room console. Two code families ride this
path, and they are about different people:

* `VIDEO_CHAT_LOBBY_*`, `VIDEO_CHAT_GUEST_FORBIDDEN`, and
  `VIDEO_CHAT_POLICY_UNAVAILABLE` are returned **to the guest** (or to an
  integration posting on the guest's behalf) when a chat send is refused.
* `VIDEO_WAITING_ROOM_FAILED`, `VIDEO_WAITING_ROOM_ADMIT_FAILED`, and
  `VIDEO_WAITING_ROOM_DENY_FAILED` are returned **to the host** when one of
  the host's waiting-room operations fails. A
  `VIDEO_WAITING_ROOM_DENY_FAILED` is a server fault while recording the
  host's deny — it says nothing about the guest, and the guest's lobby
  session is untouched by it.

## The decoder — every code on the waiting-room and lobby path

| Code | HTTP | Who sees it | What it means, and the move that clears it |
| - | - | - | - |
| `VIDEO_CHAT_LOBBY_HELD` | 403 | Guest | The room has the waiting room armed and this guest is still held in the lobby (or is no longer connected to the room). The host must admit the guest from the room console; after the admission lands, the same session's chat sends pass. See [Waiting-room hold (chat)](/reference/glossary). |
| `VIDEO_CHAT_GUEST_FORBIDDEN` | 403 | Guest or integration | The send carried no `join_token` the room's invite mirror matches, or the `livekit_token` does not verify for this room (missing, unverifiable, or minted for another room), or the chat policy read could not bind a tenant. Deterministic — a fresh join from a fresh invite link is the fix; a retry of the same payload never passes. |
| `VIDEO_CHAT_LOBBY_CHECK_UNAVAILABLE` | 503 | Guest | The platform could not complete the admission check (a transient media-layer or tenancy error), so the message is not delivered. Transient — retry with backoff. |
| `VIDEO_CHAT_POLICY_UNAVAILABLE` | 503 | Guest | The room's chat content policy could not be read before broadcast, so the message is refused rather than posted unscreened. Transient — retry with backoff. |
| `VIDEO_WAITING_ROOM_FAILED` | 502 | Host | Arming or changing the waiting-room state failed. Retry the operation. |
| `VIDEO_WAITING_ROOM_ADMIT_FAILED` | 502 | Host | The admit of a lobby-pending participant failed; the guest remains held until a retry succeeds. Retry, then check the room console for the lobby state. |
| `VIDEO_WAITING_ROOM_DENY_FAILED` | 502 | Host | The deny of a lobby-pending participant failed server-side. The guest stays in the lobby, unchanged; retry the deny or use the console's deny control. |

The two 403s differ in their fix: `VIDEO_CHAT_LOBBY_HELD` clears when the
**host** acts (admit), while `VIDEO_CHAT_GUEST_FORBIDDEN` clears when the
**guest** re-joins with a token the room recognizes. The two 503s clear on
their own; they are the platform failing closed rather than delivering a
message it could not screen.

## Host-side ladder — admitting and denying from the room console

The host works the lobby from the room view in the developer console; the
console walkthrough, including the lobby panel, is in
[Video room consoles](/guides/video-room-consoles). The underlying calls are
`POST /api/v1/video/rooms-scheduled/:id/waiting-room/admit/:identity` and
`POST /api/v1/video/rooms-scheduled/:id/waiting-room/deny/:identity`.

What changes for the guest's chat sends on each outcome:

1. **Admit** promotes the held guest from the lobby tier into the meeting.
   The admission flips the guest's live permission in place — no new token
   is minted for it — so a guest whose send was refused with
   `VIDEO_CHAT_LOBBY_HELD` can simply re-send after the admission lands, and
   the same `livekit_token` now passes the lobby gate.
2. **Deny** terminates the held guest's session. A denied guest cannot
   retry their way back in; if the host changes their mind, issue the guest
   a fresh invite and admit them when they rejoin.
3. **A failed admit or deny** (`VIDEO_WAITING_ROOM_ADMIT_FAILED` /
   `VIDEO_WAITING_ROOM_DENY_FAILED`) leaves the lobby exactly as it was.
   The guest sees nothing from your failed deny; retry the operation before
   interpreting the guest's next refusal as a lobby verdict.

If the room is configured with `auto_promote`, the first host entry lifts
every held guest at once — a `VIDEO_CHAT_LOBBY_HELD` refusal on a room with
`auto_promote` set usually means no host has joined yet, which is the lobby
working as configured.

## Guest-side moves — tokens that pass and tokens that never will

* **Hold a fresh invite token.** The `join_token` on a guest chat send must
  match a live invite for the room. If the link is stale, revoked, or
  hand-edited, the send is refused with `VIDEO_CHAT_GUEST_FORBIDDEN` and no
  retry helps — rejoin from a fresh join link so the page loads a current
  invite token.
* **Never reuse a `join_token` across rooms.** The token is bound to the
  room that issued it; a send for one room carrying another room's token
  matches nothing and is refused for the same reason as a missing token.
* **After admission, re-send first and re-fetch tokens only if the session
  dropped.** An admit changes the guest's live permission, not their
  tokens, so the existing session's chat sends pass as soon as the
  admission lands. If the tab was reloaded or the connection dropped since
  the admit, rejoin from the invite link to fetch a fresh LiveKit token and
  then send.

## What never to do

* **Do not retry a `VIDEO_CHAT_GUEST_FORBIDDEN` with the same payload
  expecting success.** The refusal is deterministic: a token the route
  cannot match will not match on the tenth retry. Fetch a fresh invite,
  rejoin, then send.
* **Do not call the room chat endpoint with a stale token.** A token older
  than the guest's live session, or minted for a different room, is a
  guaranteed 403; it also masks the real lobby state, because the lobby
  gate is only reached after the token verifies.
* **Do not read `VIDEO_WAITING_ROOM_DENY_FAILED` (or
  `VIDEO_WAITING_ROOM_ADMIT_FAILED`) as a guest error.** Both are
  host-side operation failures — a server fault while recording the
  decision — and the guest's lobby state is unchanged by them.
* **Do not loop a 503 without backoff.**
  `VIDEO_CHAT_LOBBY_CHECK_UNAVAILABLE` and
  `VIDEO_CHAT_POLICY_UNAVAILABLE` fail closed on purpose; a tight retry
  loop adds load to the degraded layer without clearing it faster.

## Escalation evidence — the support bundle

Work the sections above first; most refusals are a stale invite, an
un-admitted guest, or a host operation that needs one retry. When a refusal
survives a fresh join link and a confirmed host decision, open a ticket
with:

1. **`request_id`** — from `meta.request_id` on the refused response.
2. **Room id** — the UUID in the route path.
3. **`join_token`, redacted** — first and last four characters only; never
   paste a full invite token into a ticket.
4. **Timestamp in UTC** of the refusal, and whether 503s repeated under
   backoff.
5. **The error code verbatim**, and which side saw it — a guest chat
   refusal or a host waiting-room operation.
6. **Lobby state** — whether the waiting room was armed, whether
   `auto_promote` was set, and whether the host had joined when the refusal
   fired.

## See also

* [Video guest chat refusals](/troubleshooting/video-chat-guest-forbidden) —
  the fail-closed tenant-resolution, lobby-admission, and policy-read gates
  on `POST /api/v1/video/rooms/:id/chat`, with worked request/response
  envelopes.
* [Video room consoles](/guides/video-room-consoles) — the host-side lobby
  panel: arming the waiting room, admitting, and denying.
* [Video room lifecycle failures](/troubleshooting/video-room-lifecycle) —
  join-token mint refusals and capacity gates ahead of the lobby.
* [Error codes reference](/reference/error-codes) — the full `VIDEO_*`
  code families.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.