Feedback Community Room Platform Developers Docs API Reference AI Agents Create a Room فارسی
Documentation

Build with VideoRayan

Everything you need to go from "meeting link" to "embedded product feature". Pair this guide with the API reference for exact schemas.

Getting started

VideoRayan has two integration surfaces. Start with whichever matches your product today — you can switch later without touching client code, because both surfaces produce the same thing: a LiveKit token for wss://rtc.videorayan.ir.

No key

Link-based (5 minutes)

Create a room from curl or the hosted page, share /r/{roomId}, done. Guests enter a display name and join. Best for meetings, consultations, and internal tools.

Public API →
Bearer key

Owned rooms (an afternoon)

Your backend creates tenant rooms, mints host/guest tokens per user, and controls the lifecycle. Best for products where calls are a feature, not a link.

Backend guide →
fastest-possible-demo.sh
# create a room and open the hosted UI — that's the whole demo
ROOM=$(curl -s -X POST https://api.videorayan.ir/v1/public/rooms \
  -H "Content-Type: application/json" -d '{"name":"my-first-room"}' | jq -r .roomId)

open "https://en.videorayan.ir/r/$ROOM"   # macOS (linux: xdg-open)

Core concepts

Four ideas cover 95% of the platform. Everything else is parameters.

ConceptWhat it isRules that matter
Room The meeting container with a lifecycle (active → ended/expired). Free rooms: name is the handle, 12 h default TTL, 50-participant default cap. Tenant rooms: UUID + your externalId, TTL up to 24 h, operator-set cap.
Ref How you address a room: its UUID or, for public rooms, its name. Works in /v1/public/rooms/{ref}, /v1/guest/rooms/{ref}/tokens, and the hosted UI /r/{ref}.
Token A signed LiveKit JWT scoped to exactly one room with minimal grants. Default TTL 10 min (max 1 h). Not renewable — mint a new one per session. Treat as a credential: never persist it server-side.
Identity The participant's unique name inside the room. 1–40 chars on the guest surface, ≤128 on tenant tokens. Duplicates can't coexist in the same room — the newer connection replaces the older one.
Roles. Tenant tokens can be host or guest; guest-surface tokens are always guest. Roles map to LiveKit grants server-side — the client cannot self-upgrade.

Guide: the browser join flow

This is the exact flow the hosted join page implements — replicate it when you build your own join screen.

  1. Resolve the ref. GET /v1/public/rooms/{ref} → learn the canonical roomId, display name, and whether needsPassword is true. Show the password field only when needed.
  2. Collect identity + media consent. Ask for a display name; request camera/mic with getUserMedia so the user can preview before joining. Remember their mic/cam choices and apply them after connect.
  3. Mint the token. POST /v1/guest/rooms/{roomId}/tokens with the identity (and password when required).
  4. Handle the outcomes. 201 → connect; 403 → wrong password, re-prompt; 409 → room full/ended; 404 → bad link, send home.
  5. Connect. new Room().connect('wss://rtc.videorayan.ir', token) — with one retry on transient failure.
join-flow.js (browser)
const API = 'https://api.videorayan.ir/v1';

async function joinRoom(ref, identity, password) {
  // 1 — resolve (also validates the link before asking the user anything)
  const info = await fetch(`${API}/public/rooms/${encodeURIComponent(ref)}`).then(r => r.json());
  if (!info.roomId) throw new Error('room_not_found');

  // 2 + 3 — mint the token (password only when the room needs one)
  const res = await fetch(`${API}/guest/rooms/${info.roomId}/tokens`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ identity, ...(info.needsPassword ? { password } : {}) })
  });

  // 4 — handle outcomes explicitly
  if (res.status === 403) throw new Error('wrong_password');
  if (res.status === 409) throw new Error('room_full_or_ended');
  if (!res.ok) throw new Error('token_failed_' + res.status);
  const { token } = await res.json();

  // 5 — connect (single retry on transient failure)
  const { Room } = await import('livekit-client');
  const room = new Room({ adaptiveStream: false, dynacast: true });
  await room.connect('wss://rtc.videorayan.ir', token);
  return room;
}

Guide: backend (tenant) flow

For products that own rooms. The API key lives in your backend environment only — the browser never sees it.

1 — Create the room when your domain event happens

An appointment is booked, a support ticket escalates, a game match starts. Create the room then, with an idempotency key so retries never duplicate rooms:

create-room.ts (your backend)
async function ensureRoom(appointmentId: string) {
  const res = await fetch('https://api.videorayan.ir/v1/rooms', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.VIDEORAYAN_KEY!}`,
      Content-Type: 'application/json',
      Idempotency-Key: `apt-${appointmentId}`,   // same key → same room on retry
    },
    body: JSON.stringify({
      externalId: appointmentId,      // your correlation ID
      maxParticipants: 3,            // pilot cap
      expiresInSeconds: 3600,        // 1-hour consultation window
    }),
  });
  if (!res.ok) throw new Error('room create failed: ' + res.status);
  return res.json() as Promise<{ roomId: string; status: string }>;
}

2 — Mint a token per participant, per session

mint-token.ts (your backend)
async function joinAs(roomId: string, user: { id: string; name: string; isDoctor: boolean }) {
  const res = await fetch(`https://api.videorayan.ir/v1/rooms/${roomId}/tokens`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.VIDEORAYAN_KEY!}`,
      Content-Type: 'application/json',
    },
    body: JSON.stringify({
      identity: user.id,                       // unique per room
      name: user.name,
      role: user.isDoctor ? 'host' : 'guest',
      ttlSeconds: 600,
    }),
  });
  return res.json() as Promise<{ token: string; wsUrl: string; expiresAt: string }>;
}

3 — Connect from your front-end

consultation.tsx (React)
// token comes from YOUR backend endpoint, which called mint-token() above
import { LiveKitRoom, VideoConference } from '@livekit/components-react';

export function Consultation({ token, wsUrl }: { token: string; wsUrl: string }) {
  return (
    <LiveKitRoom token={token} serverUrl={wsUrl} connect={true} style={{ height: '100vh' }}>
      <VideoConference />
    </LiveKitRoom>
  );
}

4 — End the room when the appointment is over

DELETE /v1/rooms/{roomId} disconnects everyone, rejects new tokens, and blocks re-join. Status becomes ended (or expired via the sweeper).

Remember to mint fresh tokens on reconnect. If a user refreshes the page mid-call, their old token is usually still valid (TTL 10 min) — but never cache tokens across sessions. Mint per join.

Hosted UI & invite links

The English portal ships a production-ready UI you can use instead of building your own — every route also accepts ?room= for static hosting.

URLPageNotes
/r/{roomId}In-call UIThe canonical invite target. Desktop & mobile in one responsive page.
/join/{ref}Join (device check)Resolves the room, asks for identity/password, then redirects into the call.
/createCreate roomFree rooms with optional password; produces link + QR + share buttons.
/?room=… variantsStatic-hosting fallbacke.g. /join.html?room={ref} — useful behind static hosts without rewrites.
Branding tip. Build your own invite URL in your product: https://en.videorayan.ir/r/{roomId} needs zero integration; your own https://app.yourdomain.com/call/{externalId} can resolve externalId → roomId in your DB and redirect.

In-call features

Mobile & desktop SDKs

Swift (iOS)
import LiveKitSDK

let room = Room()
try await room.connect(url: "wss://rtc.videorayan.ir",
                     token: token)  // minted by your backend
Kotlin (Android)
val room = Room()
room.connect("wss://rtc.videorayan.ir", token)

// Flutter is identical:
// await room.connect('wss://rtc.videorayan.ir', token);

Security practices

Production checklist

Troubleshooting

SymptomCauseFix
429 on room createPer-IP rate limit hit (shared NAT, load test)Back off exponentially; for load tests contact the operator for a dedicated tenant key.
403 on guest tokenWrong/missing room passwordResolve the room first and collect the password when needsPassword is true.
409 on tokenRoom full (cap) or ended/expiredSurface "room is full/ended"; create a new room for a new session.
404 on resolveBad link, or room expired (12 h default for free rooms)Create a fresh room; don't treat 404 as retryable.
Token works locally, fails in prodToken minted for a different room or expired in transitMint per join, use immediately; check clock skew if you self-mint.
Media connects, no audio from a participantParticipant never granted mic permission, or OS-level muteCheck getUserMedia errors; the platform requests audio on every join.

FAQ

Do I need an API key to try VideoRayan?
No. The public room API is open: create a room with a name, share the /r/{roomId} link, and guests join with a display name. API keys are only needed for tenant-owned rooms with roles and server-side caps.
How long do join tokens live?
Default 10 minutes, minimum 30 seconds, maximum 1 hour. Mint a fresh token per session from your backend (or the guest endpoint for free rooms).
How many participants can a room have?
Free public rooms default to 50 participants (server-configurable). Tenant-owned pilot rooms default to 3 and can be raised to 10 by the operator.
Is media encrypted?
Yes — DTLS-SRTP protects all media between participants and the self-hosted SFU, and signaling runs over WSS. E2EE is not enabled in the first version.
Can I embed VideoRayan in my own product?
Yes. Either link to the hosted room UI at /r/{roomId}, or connect directly from your own front-end using any LiveKit SDK with a token you minted server-side. Both use the same rooms.
What happens when a room expires?
A sweeper marks the room expired every 60 seconds and deletes it from the media server, so stale tokens cannot rejoin. Create a new room for the next session.

Questions answered? Start building.

Create a free room right now — the whole public API is open, no signup.

Community & Voice

Talk live — and shape the roadmap

A free community room for open discussion (voice, video and text chat, no account) and a feedback channel the operator actually reads.