Skip to main content

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).
The response gives you the two values you need in the next step — token and livekit_url:

Step 2 — Connect the participant with the token

Pass the token and the URL to Room.connect(). This is the “where do I use it” answer: the token is the second argument to connect().
Prefer a prebuilt room widget? 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 reports lobby_downgraded: true. Your client should treat that flag as a signal, not an error:
Admission is handled server-side — the host admits from the dashboard moderation panel (or 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 as expires_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_seconds minus 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_uses and expires_in_seconds on 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’s token as rejoin_token; without it the redeem returns a new, suffixed identity.
  • 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:
Embed the returned 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.request escape 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.