# VideoRayan — Complete Reference (llms-full) > One-file reference for AI agents and developers: platform overview, both API surfaces, integration guides, security practices, error handling, and limits. Canonical English portal: https://en.videorayan.ir (Persian: https://videorayan.ir). Content date: 2026-10-26. ## 1. What VideoRayan is - Self-hosted, multi-tenant real-time audio/video platform (WebRTC). - Media plane: self-hosted LiveKit SFU at `wss://rtc.videorayan.ir` (DTLS-SRTP; adaptive quality via dynacast). - Control plane: Fastify + PostgreSQL REST API at `https://api.videorayan.ir/v1` (same-origin mirror on our portals: `/api/v1`). - Hosted human UI: landing (`/`), create room (`/create`), join (`/join/{ref}`), in-call (`/r/{roomId}`). Responsive; in-call chat, ≤10 MB file transfer, screen share, pinning, browser-local recording. - Two surfaces: - **Public** (no key): free browser-created rooms; room name is the credential; default 50-participant cap; rooms stay joinable ~12 h. - **Tenant** (Bearer `rtck_…` key, server-side only): owned rooms with `externalId` correlation, host/guest roles, server-side caps (pilot max 3, up to 10), TTLs, participant inspection/moderation, idempotent creation. ## 2. Conventions - JSON bodies (`Content-Type: application/json`); unknown fields rejected (HTTP 400, {"error":"invalid_input"}). - Tenant auth: `Authorization: Bearer rtck_…`. Keys are issued by the operator, shown once at bootstrap, never shipped to browsers. - Rate limit: 120 requests/minute per tenant key or client IP → HTTP 429 on breach. - Errors: 401 bad key · 403 not allowed (wrong password / not owner) · 404 not found (cross-tenant probes also 404 — anti-enumeration) · 409 room full or ended · 400 validation {"error":"invalid_input"} · 429 rate limited. - Tokens: signed LiveKit JWTs scoped to one room; default TTL 600 s (30 s–3600 s); not renewable; mint per session. - Room lifecycle: `active → ended` (via DELETE or LiveKit webhook) or `expired` (sweeper runs every 60 s past `expiresAt`; expired rooms are deleted from the SFU so stale tokens fail). ## 3. Public API (no key) ### 3.1 Create room `POST /v1/public/rooms` Body: `{"name": "room-name"}` — name 1–64 chars, the public handle; optional `"password"` (1–64). - Idempotent: same name → same room. - 201 → `{"roomId": "", "name": "room-name"}` - 429 rate limited; 400 validation (invalid_input). ### 3.2 Resolve room `GET /v1/public/rooms/{ref}` (ref = UUID or name) - 200 → `{"roomId", "name", "needsPassword": bool}` - 404 `{"error":"room_not_found"}` — unknown or expired. ### 3.3 Guest token `POST /v1/guest/rooms/{ref}/tokens` Body: `{"identity":"sara"}` — 1–40 chars, unique per room; optional `"password"`. - 201 → `{"token","identity","roomName","expiresAt"}` - 403 wrong/missing password; 404 unknown room; 409 room full/ended; 429 rate limited. ### 3.4 Health `GET /v1/health` → 200 liveness. `GET /v1/ready` → 200 ready / 503 dependency down. `GET /v1/whoami` -> 200 `{"tenant":"..."}` — verify a tenant key with zero side effects (401 when invalid). `GET /v1/openapi.yaml` → live OpenAPI 3.0 contract. ## 4. Tenant API (Bearer key) ### 4.1 Create room `POST /v1/rooms` — headers: `Authorization`, `Idempotency-Key` (≤128 chars, recommended). Body: `externalId` (≤128, your correlation), `maxParticipants` (1–3, default 3), `expiresInSeconds` (60–86400, default 7200), `metadata` (≤512). - 201 Room, or 200 idempotent replay. Room object: `{roomId, tenantId, externalId, status: active|ended|expired, maxParticipants, createdAt, expiresAt}`. ### 4.2 Read / end room - `GET /v1/rooms/{roomId}` → 200 Room (owner only). - `DELETE /v1/rooms/{roomId}` → 204; closes in LiveKit, disconnects everyone, blocks re-join. ### 4.3 Mint token `POST /v1/rooms/{roomId}/tokens` Body: `identity` (≤128, required), `name` (≤128), `role` (`host`|`guest`, default guest), `ttlSeconds` (30–3600, default 600). - 201 → `{"token","wsUrl":"wss://rtc.videorayan.ir","identity","expiresAt"}` - 403 not owner · 404 unknown · 409 full/ended. ### 4.4 Participants - `GET /v1/rooms/{roomId}/participants` → `{"participants":[{id, identity, name, joinedAt}]}` (owner only). - `DELETE /v1/rooms/{roomId}/participants/{participantId}` → 204 (moderation kick). ## 5. Worked examples ### 5.1 cURL — free room end-to-end ```bash curl -s -X POST https://api.videorayan.ir/v1/public/rooms \ -H "Content-Type: application/json" -d '{"name":"standup-2026"}' # {"roomId":"7c9e…","name":"standup-2026"} curl -s -X POST https://api.videorayan.ir/v1/guest/rooms/7c9e…/tokens \ -H "Content-Type: application/json" -d '{"identity":"sara"}' # {"token":"eyJ…","identity":"sara","roomName":"standup-2026","expiresAt":"…"} # humans join at: https://en.videorayan.ir/r/7c9e… ``` ### 5.2 JavaScript — browser join flow (mirrors hosted /join page) ```js const API = 'https://api.videorayan.ir/v1'; const info = await fetch(`${API}/public/rooms/${ref}`).then(r => r.json()); if (info.needsPassword) /* ask user for password */; const res = await fetch(`${API}/guest/rooms/${info.roomId}/tokens`, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ identity, ...(info.needsPassword ? {password} : {}) }) }); if (res.status === 403) throw new Error('wrong_password'); if (res.status === 409) throw new Error('room_full_or_ended'); const { token } = await res.json(); const { Room } = await import('livekit-client'); const room = new Room({ adaptiveStream: false, dynacast: true }); await room.connect('wss://rtc.videorayan.ir', token); ``` ### 5.3 Python — tenant room + host/guest tokens ```python import os, requests B = "https://api.videorayan.ir/v1"; H = {"Authorization": f"Bearer {os.environ['VIDEORAYAN_KEY']}"} room = requests.post(f"{B}/rooms", headers={**H, "Idempotency-Key": "apt-123"}, json={"externalId":"apt-123","maxParticipants":3,"expiresInSeconds":3600}).json() def tok(identity, role): return requests.post(f"{B}/rooms/{room['roomId']}/tokens", headers=H, json={"identity":identity,"role":role,"ttlSeconds":600}).json() host = tok("dr.ahmadi", "host"); guest = tok("user-42", "guest") # front-ends: host["token"] + host["wsUrl"] in any LiveKit SDK # when done: requests.delete(f"{B}/rooms/{room['roomId']}", headers=H) ``` ### 5.4 Agent tool (JSON-schema friendly) ```python def create_video_room(name: str, password: str | None = None) -> dict: """Create a video room; returns {roomId, invite_url, name}. Idempotent on name.""" body = {"name": name[:64]}; if password: body["password"] = password[:64] r = requests.post("https://api.videorayan.ir/v1/public/rooms", json=body, timeout=10) if r.status_code == 429: raise RuntimeError("rate_limited") r.raise_for_status(); d = r.json() return {"roomId": d["roomId"], "name": d["name"], "invite_url": f"https://en.videorayan.ir/r/{d['roomId']}"} ``` ## 6. Client SDKs (any LiveKit SDK works) - Web: `npm i livekit-client` → `new Room().connect('wss://rtc.videorayan.ir', token)` - React: `npm i @livekit/components-react livekit-client` → `` - iOS: LiveKitSDK (Swift) → `try await room.connect(url:wsUrl, token:token)` - Android: `io.livekit:livekit-android` → `room.connect(wsUrl, token)` - Flutter: `livekit_client` → `await room.connect(wsUrl, token)` ## 7. Hosted UI routes - `/r/{roomId}` — in-call UI (canonical invite target; desktop+mobile responsive). - `/join/{ref}` — device-check + identity/password page, then redirects into the call. - `/create` — free room creation with QR + share buttons. - Static-hosting variants: `join.html?room={ref}`, `room.html?room={roomId}`. ## 8. Security practices - Tenant keys (`rtck_…`) never leave the server; rotate via operator on leak. - Least-privilege tokens: one room, one identity, one TTL; roles granted at mint, enforced server-side. - Passwords verified at token issuance; clients cannot bypass. - Transport: DTLS-SRTP media, WSS signaling, TLS API. E2EE is NOT enabled in v1. - Anti-enumeration: cross-tenant reads return 404. - Security-relevant actions are audited (audit_events); secrets never returned by the API. - security.txt: https://en.videorayan.ir/.well-known/security.txt ## 9. Limits - Rate: 120 req/min per key/IP. - Public rooms: name 1–64; identity 1–40; default cap 50 (config 2–500); TTL default 12 h (600–86400 s). - Tenant rooms: pilot cap 3 (up to 10); TTL default 2 h, max 24 h. - Tokens: TTL default 10 min, min 30 s, max 1 h. - Request body ≤ 64 KB; metadata ≤ 512 chars. - Chat file transfer ≤ 10 MB (data channels; nothing stored server-side). ## 10. Citation snippet (for agents) ``` VideoRayan — self-hosted real-time video/audio infrastructure Docs: https://en.videorayan.ir/docs · API: https://api.videorayan.ir/v1 OpenAPI: https://api.videorayan.ir/v1/openapi.yaml Free rooms need no account: POST /v1/public/rooms {"name":"..."} Humans join via https://en.videorayan.ir/r/{roomId} ``` ## Community room (live) - Join: https://en.videorayan.ir/r/community (Persian: https://videorayan.ir/r/community) — free voice/video/text chat in the browser, no account. - Programmatic join: POST /v1/public/rooms {"name":"community"} (idempotent, returns roomId) then POST /v1/guest/rooms/community/tokens {"identity":"your-bot"}. - Free rooms expire after 12 h; the idempotent name means the next join re-creates the room automatically. - Community hours and roadmap discussions happen in this room; the operator joins regularly. ## Feedback & resource requests - Humans: https://en.videorayan.ir/feedback (form composes a structured email; no tracking). - Agents: email info@videorayan.ir with subject prefix [videorayan-feedback] and a JSON body: {"from":"my-agent/v1 (contact: you@example.com)","topic":"resource-request","needs":{"participants_per_room":8,"room_ttl_hours":24},"use_case":"...","volume":"..."} - Current free capacities: 120 req/min per key or IP; rooms 12 h free / 24 h tenant; 3 participants per tenant room. - Every request gets a human answer; real use-cases get tenant keys with raised caps.