Using a video room access token
When you create a room, join a room, or a guest redeems an invite, Orbit returns an access token — a short-lived JWT scoped to one room and one identity. This is the single most common point of confusion: you have the token in your hand, and it isn’t obvious what to do with it. The short answer: an access token is an SDK credential. You pass it to the@orbit/media-client
SDK’s Room.connect() call to put a participant into the live room. It
is not an HTTP Authorization header, and it is not a link you
send to a non-technical guest.
If you just want to get a person into a room and you are not building on
the SDK, you probably want a guest invite link, not a raw token. Use
the Share button on a room, or
POST /video/rooms-scheduled/{id}/invites,
to mint a link a guest can open in their browser. Reach for an access
token only when your own client code connects to the room.Where the token comes from
Three endpoints return an access token. They differ in who the token is for, but the token itself is used the same way in every case.
Each response also carries the media-server WebSocket URL you connect
to. Prefer
livekit_url; fall back to the legacy ws_url alias if it
is absent.
What every token response carries
Beyond the token itself, these fields tell your client how to connect and what it may do. Check them on every mint — a lobby clamp or a role downgrade changes them between calls.Choose the right tier — and how tenant roles clamp it
participant_tier is one of four values. The deeper model (tiers,
role mapping, webhook payloads) lives in
Video rooms: the model; the permission
clamps you need to pick one correctly are below.
The API clamps the tier you request by the tenant role of the caller:
A denied privileged-tier request returns
403 with an audit row and
never mints. Compare before you branch on a tier: the response’s
participant_tier is the grant to render from, not the tier you
requested.
Step 1 — Get a token
Mint a per-participant token for an existing room.identity must be
unique per person in the room; participant_tier sets what they can do
(viewer, panelist, host, hidden_supervisor).
token and livekit_url:
Step 2 — Connect the participant with the token
Pass the token and the URL toRoom.connect(). This is the “where do I
use it” answer: the token is the second argument to connect().
OrbitVideoRoom from
@devotel-orbit/web wraps exactly this flow — you hand it the same
token + media URL and it paints the tile grid, control bar, and
device picker for you. The callback-driven alternatives (per-language
quickstarts) are cataloged on the SDK index — pick the
client-side snippet that fits your page instead of wiring Room by
hand.
The
Room.connect() form shown here is the TypeScript shape. For a
Python backend minting tokens before handing them to a browser or
native client, the Python SDK’s low-level
client.request("POST", "/video/rooms-scheduled/{id}/join", json={...})
escape hatch returns the same token + livekit_url pair — use it
when the typed Python surface doesn’t cover video yet. See
Python SDK.The upstream
livekit-client SDK still works at the protocol level, but
@orbit/media-client is the supported client. If you use livekit-client,
point it at the livekit_url from the response — never at livekit.cloud,
which was retired.Waiting-room semantics — the lobby clamp
When a room has its waiting room armed, a non-host join is held: the join succeeds but mints a receive-only token and reportslobby_downgraded: true. Your client should treat that flag as a
signal, not an error:
POST /video/rooms-scheduled/{id}/waiting-room/admit/{identity});
an admitted identity re-joins on the full grant without minting a new
token. auto_promote: true on the waiting-room endpoint lifts the hold
for everyone once the first host enters, which suits open-office-hours
style rooms. The full waiting-room surface (arm/disarm, admit, promote)
is documented on the Video channel page.
Other refusal gates sit in front of the token entirely: a locked room,
a ban list hit, a webinar registration that was never approved, and a
join against a room whose session already ended. All four return a 4xx
refusal before any JWT is minted, so a successful response always means
a usable token.
Tokens expire — re-mint before they do
An access token is valid for a limited window: the default mint TTL is 3600 seconds (1 hour), reported asexpires_in_seconds from issue
and reflected in the absolute expires_at. Re-mint a fresh token and
reconnect before expires_at to avoid dropping mid-session. Requesting
a new token for the same identity is safe — the service treats it as a
rotation of the same participant, not a duplicate join.
Rotation guidance:
- Mint as close to connect time as practical; do not hold tokens in long-lived config or browser storage.
- For long rooms, schedule the re-mint from
expires_in_secondsminus a small margin, then reconnect with the fresh JWT. - Do not reuse a token after its room session moved past
expires_at— the media server refuses it, and the client surfaces a connect error rather than a silent drop.
Rate limits and reuse boundaries
Join, invite, and redeem endpoints sit in the authenticated-write rate bucket shared with your other API calls. The bounded claims you can rely on:- Reuse: minting for the same identity twice is safe; the newer token supersedes nothing on the server — both remain valid until their own TTL lapses. A bad actor holding an old token can still join until it expires, so rotate by identity (e.g. append a session suffix) when you need hard revocation.
- Guest invites: bound
max_usesandexpires_in_secondson the invite itself — a redeemed token still occupies a seat even after the invite link dies. A guest re-redeeming under an identity the room has already seen must send the earlier redeem’stokenasrejoin_token; without it the redeem returns a new, suffixedidentity. - Volume: token mints are cheap, but A/B-test warm-up and polling dashboards mint in bursts — send sustained join traffic through the same retry-with-backoff discipline you use for other write endpoints.
Handle it like a credential
Anyone holding a valid token joins the room as the named identity, so treat it like a password:- Deliver it to your client over a secure channel (your own authenticated API), not by pasting it into chat or email.
- Do not log it or embed it in a shareable URL.
- To invite a human who is not running your integration, send a guest invite link instead of a token — see below.
Inviting a guest instead
If the person joining is not your own SDK client — a customer, a vendor, anyone without your integration — mint a guest invite link rather than a raw token. The link redeems to a token on the server for you, so the guest never touches a JWT:invite_token in a URL and send that. In the
dashboard, the Share button on a room does exactly this.
Reference
For the full request and response schema of every endpoint mentioned here — create, join, invite, redeem, and the complete field list — see the Video API reference.See also
- Video rooms: the model — tier definitions, effective-grant derivation, and the room lifecycle.
- Video channel page — the waiting-room endpoints (arm, admit, promote) and per-tier grant matrix.
- Prebuilt video room (OrbitVideoRoom) — drop-in widget that
wraps the
Room.connect()flow. - Python SDK — the
client.requestescape hatch for a Python backend that mints tokens server-side. - AI video avatar agents — the avatar-bot dispatch path that joins through the same token seam, so every gate described here applies to bot joins identically.