Getting Started — Vibe Coding Guide
What is this?
OpenVibe.Live is a self-hosted, open-source live streaming platform with interactive controls, chat bots, TTS, soundboards, and hardware bridges. This documentation is designed for vibe coding — copy the section you need into your LLM and start building.
Quick Architecture Overview
| Layer | Tech | Notes |
| Backend | Node.js + Express | All routes return JSON |
| Database | PostgreSQL | via openvibe-sdk/db |
| WebRTC SFU | Mediasoup | Video/audio routing |
| RTMP | Node-Media-Server | OBS classic ingest |
| Chat/Control | WebSocket (ws) | Real-time messaging |
| Frontend | Vanilla JS SPA | No framework |
| Auth | RS256 JWT via openvibe.network SSO | Shared across OpenVibe |
Which doc to copy for which task?
| Building a... | Copy this tab |
| Chat bot / overlay | Chat — WS protocol, bot examples, commands |
| Robot/hardware bridge | Control WS — full Python + Node.js bridge examples |
| Custom stream player | WebRTC — WHIP, SFU, SDP protocol |
| Stream management tool | Streams — CRUD, go-live, channels |
| TTS / soundboard bot | TTS & Audio — voice catalog, audio queue, 101soundboards |
| Discord/push integration | Integrations — events, webhooks, PowerChat, restream, RobotStreamer |
| Tipping / points / payments | Money — Vibes economy, donations, cashout, subscriptions, OpenCoins |
| AI bots / chat insight | AI — chat-AI insight + AI chat viewers |
| Control panel / buttons | Controls — profiles, button schema, settings |
| Admin / everything else | API Ref — chat, emotes, sounds, DMs, analytics, rate limits |
How the OpenVibe is split
| Service | URL | Owns |
| OpenVibe.Live | openvibe.live | Streaming, chat, TTS, controls, VODs, soundboard |
| openvibe.network | openvibe.network | SSO, accounts, notifications, push, Discord bot, admin |
| openvibe.games | openvibe.games | Games (RPG, canvas) |
Base URL & Auth
Base URL: https://openvibe.live (replace with your instance).
Auth: Authorization: Bearer <JWT> header. On WebSockets, the header on the upgrade request or the token of the first join message. A token in the URL or a cookie does not sign a socket in.
API tokens: hbt_ prefix, scoped permissions, created from Dashboard → API Tokens.
Network Architecture
Service Map
┌──────────────────────────────────────────────────────┐
│ openvibe.network (port 3100) — Central Hub │
│ ├─ /api/auth — SSO (RS256 JWT) │
│ ├─ /api/notifications — Notification CRUD │
│ ├─ /api/push — Web Push (VAPID) │
│ ├─ /api/admin — Unified admin panel │
│ ├─ /api/admin/discord — Discord bot config │
│ ├─ /internal — Server-to-server API │
│ └─ Discord bot — Live alerts, system alerts │
├──────────────────────────────────────────────────────┤
│ OpenVibe.Live (port 3000) — Streaming Platform │
│ ├─ /api/streams — Stream CRUD + go-live │
│ ├─ /api/chat — Chat REST API │
│ ├─ /api/tts — TTS voice config │
│ ├─ /ws/chat — Real-time chat │
│ ├─ /ws/broadcast — WebRTC signaling │
│ ├─ /whip/:id — WHIP WebRTC ingestion │
│ └─ RTMP server — rtmp://host/live │
└──────────────────────────────────────────────────────┘
Event Flow: Stream Goes Live
1. Streamer goes live (Go-Live API, RTMP, WHIP or the OpenRestream mirror)
2. OpenVibe.Live creates the stream record and publishes live.stream.started to OpenVibe.Events
3. openvibe.network's Events consumer tells the streamer's followers (its own follows):
inbox notification, Web Push (VAPID), email and Discord, at most once per streamer per window
4. A go-live while openvibe.network is down waits in OpenVibe.Events and is delivered on retry
WebSocket Services
| Path | Purpose | Auth |
| /ws/chat | Chat rooms (per-stream or global) | Optional (anon allowed) |
| /ws/broadcast | WebRTC SFU signaling | JWT required |
| /ws/call | Group voice/video calls | JWT required |
Admin Proxy Architecture
openvibe.network admin panel proxies requests to OpenVibe.Live:
openvibe.network /api/admin/streamer/* → OpenVibe.Live /api/admin/*
openvibe.network /api/admin/streamer-tts/* → OpenVibe.Live /api/tts/*
openvibe.network /api/admin/streamer-mod/* → OpenVibe.Live /api/mod/*
Server-to-server calls carry a short-lived service token from openvibe.network, scoped to the one capability each route performs.
1. AUTHENTICATION
All identity is OpenVibe SSO. There is no local password login on OpenVibe.Live — POST /api/auth/register and POST /api/auth/login are permanently disabled (return 410). Humans sign in through openvibe.network OAuth2; OpenVibe.Live receives an RS256 JWT signed by openvibe.network and verifies it locally. For bots and scripts, use a long-lived hbt_ API token (below) — no browser login needed.
How auth is sent
Every authenticated request carries a token as Authorization: Bearer <token> (REST also reads the ov_token cookie; a WebSocket takes only the header or the token in its first join message). requireAuth accepts either:
- API token — a string starting with
hbt_. Best for bots/integrations. Cannot itself create/list/revoke tokens.
- openvibe.network JWT (RS256) — from the SSO login flow; auto-links/creates a local account. Roles come from the token.
Roles: admin > global_mod > streamer > user (+ anonymous). Banned accounts get 403.
REST Endpoints
| Method | Path | Auth | Notes |
| GET | /api/auth/sso/login | No | 302 → openvibe.network OAuth authorize |
| GET | /api/auth/callback?code&state | No | OAuth code→token exchange; sets cookies |
| POST | /api/auth/refresh | Cookie | Refresh access token via httpOnly ov_refresh cookie |
| POST | /api/auth/logout | No | Clear auth cookies |
| GET | /api/auth/sso/status | No | SSO enabled flag + login URL |
| GET | /api/auth/me | Yes | Current user + capabilities |
| PUT | /api/auth/profile | Yes | Update display_name (recase only), bio, profile_color (email and password are the OpenVibe account's: openvibe.network/my) |
| GET | /api/auth/user/:username | No | Public profile |
| POST | /api/auth/avatar | Yes | Upload avatar (multipart avatar, ≤20MB) |
| GET / POST | /api/auth/stream-key[/regenerate] | Yes | Get / regenerate personal stream key |
| GET / PUT | /api/auth/preferences | Yes | Synced chat settings (≤16KB) |
| GET | /api/auth/ice-servers | No | STUN/TURN config for WebRTC/voice |
| POST | /api/auth/register · /login · /change-password | — | Disabled (410) — SSO only |
API Tokens (hbt_ prefix)
Long-lived, scoped tokens for bots/integrations. Create them from Dashboard → API Tokens (max 10 active). The token string is shown once. Use as Authorization: Bearer hbt_xxxxxxxxxxxx.
| Method | Path | Notes |
| POST | /api/auth/tokens | Create { label, scopes[], expiresAt? } → { id, token, ... } (JWT auth only, not an hbt_ token) |
| GET | /api/auth/tokens | List tokens + valid_scopes, scope_definitions, token_presets |
| DELETE | /api/auth/tokens/:id | Revoke a token |
Scopes: chat, read, stream, control, vibe_coding_publish. Presets: chat-bot (chat,read), stream-controller (read,stream,control), vibe-coding-publisher (read,vibe_coding_publish).
Example: use an API token
// Create the token once from Dashboard → API Tokens (scopes: chat, read),
// then use it directly — no login flow needed.
const TOKEN = 'hbt_xxxxxxxxxxxxxxxx';
const me = await fetch('https://openvibe.live/api/auth/me', {
headers: { 'Authorization': `Bearer ${TOKEN}` }
}).then(r => r.json());
console.log('Acting as:', me.user.username);
2. STREAMS & CHANNELS
Model: a managed stream is a persistent slot (a saved config: title, category, protocol, its own stream_key, plus recording/clip/privacy settings). A stream is one live session started from a slot. You do NOT create streams directly — you create a slot, then go live from it.
Managed streams (slots)
| Method | Path | Auth | Notes |
| GET | /api/streams/managed | Yes | My slots + slot limit |
| POST | /api/streams/managed | Yes | Create a slot → unique stream_key. Body: title, description, category, protocol, is_nsfw, slug, streaming_method, control_config_id |
| PUT | /api/streams/managed/:id | Owner | Update slot + slot settings (see below) |
| DELETE | /api/streams/managed/:id | Owner | Delete slot (409 if live) |
| POST | /api/streams/managed/:id/regenerate-key | Owner | New slot stream key |
Per-slot settings (PUT /managed/:id): streaming_method (browser|whip|cli|rtmp — auto-derives protocol), browser_mode (camera|mic_only|screen), default_vod_visibility / default_clip_visibility (public|unlisted|private), slot_vod_recording_enabled, slot_clip_recording_enabled, slot_clip_notify_enabled (0/1), weather fields, control_config_id.
Go live / sessions
| Method | Path | Auth | Notes |
| POST | /api/streams | Yes | Go live from a slot. Body: managed_stream_id (required) + optional title, category, tags, is_nsfw, control_config_id → { stream, endpoint } |
| GET | /api/streams/:id/endpoint | Owner | Ingest endpoint + stream_key for the session's protocol |
| PUT | /api/streams/:id | Owner | Update live session (title/description/category/tags) |
| DELETE | /api/streams/:id | Owner | End the session |
| POST | /api/streams/:id/heartbeat | Yes | Keepalive (active ingest is the authoritative live signal) |
| GET | /api/streams/:id/rtmp-status | Yes | RTMP ingest session status |
Discovery & channels
| Method | Path | Auth | Notes |
| GET | /api/streams | Opt | All currently-live streams (+ external viewer totals) |
| GET | /api/streams/mine | Yes | My streams |
| GET | /api/streams/:id | Opt | Stream detail (+ endpoint if live, controls, cameras); keys stripped |
| GET | /api/streams/recently-online | No | Streamers grouped, with their slots |
| GET | /api/streams/recent-vods | No | Recent public VODs |
| GET | /api/streams/channel/:username | Opt | Full channel page (streams, VODs, clips, panels, managed_streams) |
| GET / PUT | /api/streams/channel | Yes | Get / update my channel |
| POST | /api/streams/:id/follow · /channel/:username/follow | Yes | Follow / unfollow (toggles) |
| GET | /api/streams/rtmp-proxy/:streamId.flv | No | HTTP-FLV playback proxy for RTMP streams |
Supported ingest protocols
- webrtc (streaming_method
browser or whip) — Mediasoup SFU, lowest latency. Browser capture or a WHIP encoder (OBS 30+).
- rtmp — classic RTMP ingest (OBS/ffmpeg). Recorded losslessly to
.mp4; plays back via HTTP-FLV.
- jsmpeg (streaming_method
cli) — MPEG-TS over WebSocket for Raspberry Pi / low-power devices.
The ingest URL comes from GET /api/streams/:id/endpoint — webrtc returns { roomId, signalingUrl, whipUrlBase }, rtmp returns { rtmpUrl, streamKey, flvUrl }, jsmpeg returns ffmpeg command strings + ports.
Example: create a slot, go live, get the WHIP URL
const H = { 'Authorization': `Bearer ${TOKEN}`, 'Content-Type': 'application/json' };
// 1. Create a WHIP slot (once)
const slot = await fetch('https://openvibe.live/api/streams/managed', {
method: 'POST', headers: H,
body: JSON.stringify({ title: 'Robot cam', streaming_method: 'whip', category: 'Technology' })
}).then(r => r.json());
// 2. Go live from the slot
const { stream, endpoint } = await fetch('https://openvibe.live/api/streams', {
method: 'POST', headers: H,
body: JSON.stringify({ managed_stream_id: slot.managed_stream.id })
}).then(r => r.json());
console.log(endpoint.whipUrlBase); // POST your SDP offer here (see WebRTC tab)
3. WEBRTC BROADCAST PROTOCOL
WHIP Ingestion (OBS / ffmpeg / custom encoder)
RFC 9725 compliant. POST a raw SDP offer, receive SDP answer.
| Method | Path | Notes |
| POST | /whip/:streamId | Body: raw SDP (Content-Type: application/sdp). Auth: Bearer JWT. Returns SDP answer + Location header. |
| PATCH | /whip/:streamId/:resourceId | Trickle ICE (Content-Type: application/trickle-ice-sdpfrag) |
| DELETE | /whip/:streamId/:resourceId | Terminate WHIP session |
Broadcast WebSocket — /ws/broadcast
Connect: wss://openvibe.live/ws/broadcast?streamId=ID&role=broadcaster|viewer&token=JWT
All messages are JSON. Send: ws.send(JSON.stringify({ type: 'watch' }))
| Type | Dir | Key Fields | Description |
| welcome | S→C | peerId, role, streamId, viewerCount, iceServers | Sent immediately on connect |
| watch | C→S | — | Viewer requests to start watching (triggers SFU consumer) |
| broadcaster-ready | S→Viewer | peerId | Broadcaster or SFU source is live, safe to send/refresh watch |
| watch-queued | S→Viewer | reason, detail | Viewer is queued while the live source warms up or reconnects |
| sfu-viewer-ready | S→Viewer | rtpCapabilities, producers | Server has live SFU producers ready for consumption |
| sfu-viewer-create-transport | C→S | — | Viewer asks the server to create an SFU recv transport |
| sfu-viewer-transport-created | S→Viewer | id, iceParameters, iceCandidates, dtlsParameters, iceServers | Viewer recv transport parameters |
| sfu-viewer-connect-transport | C→S | transportId, dtlsParameters | Connect the viewer recv transport |
| sfu-viewer-transport-connected | S→Viewer | transportId | Viewer recv transport connected |
| sfu-viewer-consume | C→S | transportId, producerId, rtpCapabilities | Create an SFU consumer for a live producer |
| sfu-viewer-consumed | S→Viewer | id, producerId, kind, rtpParameters | Consumer parameters returned to mediasoup-client |
| sfu-source-unavailable | S→Viewer | reason | Live source exists, but it is not currently healthy for viewers |
| sfu-get-capabilities | C→S | — | Get Mediasoup RTP capabilities |
| sfu-create-transport | C→S | direction: send|recv | Create WebRTC transport |
| sfu-connect-transport | C→S | transportId, dtlsParameters | Connect DTLS |
| sfu-produce | C→S | transportId, kind, rtpParameters | Start producing audio/video |
| sfu-stop-produce | C→S | producerId | Stop a producer |
| viewer-count | S→All | count | Updated viewer count broadcast |
| stream-ended | S→Viewers | — | Stream has ended |
Viewer media is delivered exclusively through the Mediasoup SFU. There is no direct peer-to-peer relay: legacy offer / answer / ice-candidate messages are not supported.
RTMP Ingest (OBS)
Server: rtmp://openvibe.live/live
Key: YOUR_STREAM_KEY (get from Dashboard → Stream Key)
# Playback proxy for browser
GET /api/streams/rtmp-proxy/:streamId.flv
4. CHAT SYSTEM — CHATBOT GUIDE
Connect to the chat WebSocket to read and send messages. Perfect for building chat bots, overlays, alert systems, and integrations.
Chat WebSocket — /ws/chat
Connect: wss://openvibe.live/ws/chat?stream=STREAM_ID with an Authorization: Bearer <token> header (a JWT or an hbt_ API token), then send { "type": "join", "streamId": STREAM_ID }. A client that cannot set headers (a browser) puts the token in that join instead: { "type": "join", "streamId": STREAM_ID, "token": "…" }.
Omit stream for global chat; omit the token to connect anonymously (assigned an anonXXXXX identity). A token in the URL does not sign the socket in: URLs end up in logs.
Client → Server Messages
| Type | Payload | Description |
| chat | { message, reply_to_id?, auto_delete_minutes? } | Send a message (max 500 chars) |
| join | { streamId?, token? } | Join a stream's chat room (or global chat without streamId); the token signs an anonymous socket in |
| join_stream | { streamId, token? } | Same as join |
| leave_stream | {} | Leave current stream chat |
| get-users | {} | Request current user list |
Server → Client Messages
| Type | Key Fields | Description |
| auth | authenticated, username, role, user_id, slowmode_seconds, slur_filter_enabled | Identity confirmation, the answer to every join. |
| chat | username, core_username, user_id, anon_id, role, message, stream_id, is_global, timestamp, id, reply_to?, avatar_url, profile_color, filtered | A chat message. is_global=true means not tied to a stream. |
| system | message, timestamp | System notice (joins, events) |
| user-count | count | Updated viewer/chatter count |
| users-list | users: { logged: [{username, role, avatar_url}], anonCount } | Full user list (after get-users) |
| error | message | Error (banned, rate limited, etc.) |
| slur-blocked | message | Your message was blocked by the anti-slur filter |
| coin_earned | coins, total, reason | OpenCoins earned notification |
Chat Bot — Complete Example (Node.js)
const WebSocket = require('ws');
const STREAM_ID = 123;
const ws = new WebSocket(`wss://openvibe.live/ws/chat?stream=${STREAM_ID}`, {
headers: { Authorization: 'Bearer hbt_YOUR_TOKEN' },
});
ws.on('open', () => {
console.log('Bot connected');
ws.send(JSON.stringify({ type: 'join', streamId: STREAM_ID }));
});
ws.on('message', (raw) => {
const msg = JSON.parse(raw);
if (msg.type === 'auth') {
console.log('Logged in as:', msg.username);
}
if (msg.type === 'chat') {
console.log(`[${msg.username}] ${msg.message}`);
// Respond to commands
if (msg.message.startsWith('!hello')) {
ws.send(JSON.stringify({
type: 'chat',
message: `Hey @${msg.username}! 👋`
}));
}
if (msg.message.startsWith('!time')) {
ws.send(JSON.stringify({
type: 'chat',
message: `The time is ${new Date().toLocaleTimeString()}`
}));
}
}
});
ws.on('close', () => setTimeout(() => reconnect(), 5000));
Chat Bot — Complete Example (Python)
import json, websocket, time
TOKEN = 'hbt_YOUR_TOKEN'
STREAM_ID = 123
WS_URL = f'wss://openvibe.live/ws/chat?stream={STREAM_ID}'
def send(ws, message):
ws.send(json.dumps({ 'type': 'chat', 'message': message }))
def on_message(ws, raw):
msg = json.loads(raw)
if msg.get('type') == 'auth':
print(f"Connected as {msg.get('username')}")
if msg.get('type') == 'chat':
user = msg.get('username', '?')
text = msg.get('message', '')
print(f'[{user}] {text}')
if text.startswith('!hello'):
send(ws, f'Hey @{user}!')
if text.startswith('!uptime'):
send(ws, f'Bot uptime: {int(time.time() - start_time)}s')
def on_open(ws):
print('Connected')
ws.send(json.dumps({ 'type': 'join', 'streamId': STREAM_ID }))
def on_close(ws, *a): print('Disconnected — reconnecting...')
def on_error(ws, e): print(f'Error: {e}')
start_time = time.time()
while True:
ws = websocket.WebSocketApp(WS_URL, header=[f'Authorization: Bearer {TOKEN}'],
on_message=on_message, on_open=on_open, on_close=on_close, on_error=on_error)
ws.run_forever()
time.sleep(5)
Chat Commands (built-in)
| Command | Description |
| /tts <text> | Text-to-speech via TTS system |
| /color #hex | Set your chat name color |
| !sr / !yt / !request <url> | Media / song request |
| !queue | Show media queue |
| !np / !nowplaying | Now playing |
User Roles
| Role | Description |
| admin | Platform admin |
| mod | Platform moderator |
| streamer | Registered streamer |
| user | Registered viewer |
| anon | Anonymous visitor (anonXXXXX identity) |
Chat REST API
| Method | Path | Auth | Description |
| POST | /api/chat/send | Yes | Post a message to global chat (bots; API tokens need the chat scope) |
| GET | /api/chat/:streamId/history | No | Chat history for a stream |
| GET | /api/chat/:streamId/users | No | Users in a stream's chat |
| GET | /api/chat/user/:username/profile | No | User profile card data |
MONEY — Vibes, POINTS & PAYMENTS
The economy in one paragraph
Vibes are integer, "bit"-style units (like Twitch bits). 100 bucks = $1.00 of streamer cashout value (1 buck = 1 USD cent). Viewers buy at a premium with volume discounts (per-buck price $0.0150 down to $0.0110), streamers cash out at a flat $0.01/buck, and the spread is the platform margin. Each user has two balances: a spendable balance (bucks you bought — used to donate/subscribe) and a cashout balance (bucks you received as donations/sub share — the only balance you can cash out). Cashouts go to a 14-day escrow for admin review; minimum cashout is 500 bucks ($5.00). Two free currencies also exist: global GOLD and per-channel loyalty points (OpenCoins).
Vibes — /api/funds
| Method | Path | Auth | Notes |
| GET | /api/funds/balance | Yes | Both balances + USD cashout values (usd_value = cashout_balance / 100) |
| POST | /api/funds/donate | Yes | Donate spendable bucks to a streamer; credits their cashout balance + advances a goal. Body: amount, streamer_id|stream_id, message?, goal_id? |
| POST | /api/funds/cashout | Yes | Request cashout → escrow. Body: amount (≥ 500), paypal_email |
| POST | /api/funds/recycle | Yes | Move cashout balance → spendable (re-donate instead of cashing out) |
| GET | /api/funds/history | Yes | Transaction history (?limit ≤ 200) |
| GET | /api/funds/leaderboard/:streamId | No | Top donors for a stream |
| GET | /api/funds/goals/:userId | No | Public goal widget set (active + recently reached) |
| GET | /api/funds/goals/manage/mine | Yes | All my goals (dashboard) |
| POST / PUT / DELETE | /api/funds/goals[/:id] | Yes | Manage donation goals (title, target_amount in bucks, image_url?, media_type?) |
| GET | /api/funds/cashouts/pending | Owner | Pending (escrow) cashouts |
| POST | /api/funds/cashout/:id/approve · /deny | Owner | Release / refund a cashout |
Buying Vibes & subscriptions — /api/payments
Real-money purchases via Stripe, PayPal, CCBill, or crypto (NOWPayments). Off by default (payments_enabled). Every purchase creates an order, then a provider webhook fulfills it once (idempotent).
| Method | Path | Auth | Notes |
| GET | /api/payments/config | No | Live providers + pricing: packages, cashoutBucksPerUsd (100), minPurchaseBucks, subPriceUsd |
| POST | /api/payments/bucks/checkout | Yes | Buy bucks (USD derived from volume tiers). Body: provider, bucks → { url } hosted checkout |
| POST | /api/payments/subscribe | Yes | Subscribe to a channel. Body: provider (stripe|bucks), streamer |
| GET | /api/payments/subscriptions/mine · /channel/:username | Yes/Opt | My subs / am-I-subscribed + count + price |
| POST | /api/payments/subscriptions/:id/cancel | Yes | Cancel (Stripe at period end; bucks immediate) |
| POST/ALL | /api/payments/webhook/{stripe,paypal,ccbill,crypto} | Signed | Provider webhooks → fulfill/renew/cancel |
Packages: 100, 500, 1000, 2500, 5000, 10000, 25000 bucks. Per-buck price tiers: ≥25000→$0.0110, ≥10000→$0.0115, ≥5000→$0.0120, ≥2500→$0.0124, ≥1000→$0.0130, ≥500→$0.0140, else $0.0150. Streamers keep sub_streamer_share_pct (default 70%) of sub value into their cashout balance.
OpenCoins / Coins — /api/coins
Free currencies. GOLD is a global wallet (games, cosmetics, media requests). Channel points ("OpenCoins") are per-streamer loyalty points earned by watching/chatting a channel and spent on that streamer's rewards. Defaults: watch = 10/5min, chat = 5 (max 1/min), follow = 50 once; not earnable on your own stream.
| Method | Path | Auth | Notes |
| GET | /api/coins/balance · /channel-balance | Yes | Global GOLD / per-streamer points (?streamerId) |
| POST | /api/coins/heartbeat · /bonus | Yes | Watch-time tick / claim bonus game → earn points (streamId) |
| GET | /api/coins/rewards/:userId · /config/:userId | No | A streamer's rewards + points config |
| PUT | /api/coins/config | Yes | Configure my channel points (name, icon, watch/game rates) |
| POST / PUT / DELETE | /api/coins/rewards[/:id] | Yes | Manage my rewards (title, cost, cooldown_seconds, max_per_stream, requires_input) |
| POST | /api/coins/redeem | Yes | Redeem a reward (spends points). Body: rewardId, streamId?, userInput? |
| GET / POST | /api/coins/redemptions[/:id] | Yes | Streamer redemption queue / resolve (fulfilled|rejected) |
Cosmetics — /api/cosmetics
Chat cosmetics (name effects, hats, particles, voices) unlocked from openvibe-quest game items and equipped per slot.
| Method | Path | Auth | Notes |
| GET | /api/cosmetics/catalog | No | Full catalog grouped by category |
| GET | /api/cosmetics/inventory · /equipped/:userId | Yes/No | My unlocked+equipped / a user's equipped (for chat render) |
| POST | /api/cosmetics/equip · /unequip | Yes | Equip a slot / consume a game item to unlock / revoke |
AI SYSTEMS
OpenVibe.Live runs a background AI layer over community content (chat, streams, VODs) using a shared platform key with a per-streamer daily budget, or a streamer's own BYO key. Two developer-facing surfaces:
Chat-AI insight (read-only) — /api/chat/ai and /api/chat-ai
Rolling LLM summaries of the chat, kept by OpenVibe.Chat: a global "community vibe", plus per-user / per-anon / per-relay-user insight (overview + running memory + a short timeline). A channel's AI timeline and a user's streamer side are OpenVibe.Live's and include the chatter insight. No auth required to read.
| Method | Path | Notes |
| GET | /api/chat/ai/global | Site-wide chat overview + timeline + memory |
| GET | /api/chat/ai/timeline | The global chat timeline: ?before=, ?since= (ms), ?q=, ?limit= |
| GET | /api/chat-ai/user/:id | A user's "today vs all-time" chat insight + their streamer overview and stream memories if they broadcast |
| GET | /api/chat/ai/anon/:anonId | Anon chatter insight (id like anon12345) |
| GET | /api/chat/ai/relay/:platform/:username | Bridged external (Twitch/Kick/YouTube/RS) chatter insight |
| GET | /api/chat-ai/timeline/:username | A channel's AI timeline: streamer overview, every session's overview and VOD moments (?offset=, ?limit=) |
AI Chat Viewers — /api/ai-viewers
Persistent, context-aware AI chatter bots per channel. They read the streamer/user/global AI insight + live situation and post one at a time. All endpoints are requireAuth (streamer configures their own).
| Method | Path | Notes |
| GET / PUT | /api/ai-viewers/config | My config + budget/usage (enabled, num_ambient_bots, pacing_seconds, persona, transcribe/vision, use_shared_key, daily_budget_cents, BYO key/base/model) |
| GET | /api/ai-viewers/roster | My persistent bot roster (identity, memory, msg_count, cloned_from) |
| PATCH / DELETE | /api/ai-viewers/bots/:id | Edit (display_name/identity/is_active) / delete a bot |
| POST | /api/ai-viewers/bots/:id/clear-memory | Wipe a bot's rolling memory |
| POST | /api/ai-viewers/clone | Clone a real chatter into a bot. Body: kind (user|relay), ref (userId or platform:username) |
| POST | /api/ai-viewers/preview | Generate one sample line without posting (needs active budget) |
Budget gating (budgetStatus): shared_ai_disabled, over_daily_cap, or no_byo_key pause generation. Note: AI viewers inject chat lines server-side, so they don't appear as WS clients.
5. ROBOTS — OPENVIBE.BOT
Robots live on OpenVibe.Bot, the open control panel for robots: pairing, the drive panel, roles and turn queues, limits, the latched e-stop, video and the command audit are all there. OpenVibe.Live shows a channel's robot and lets its chat drive it; it has no control socket or hardware API of its own.
A robot on your channel
- Add a robot on openvibe.bot/robots/new: a Raspberry Pi car, a Cozmo through a computer beside it, a phone (scan a QR code, nothing to install) or an ONVIF camera. Its pairing page has the one command (or the QR code) that brings it online.
- On the robot's panel, turn on Let anyone watch where it is embedded, and choose who may drive: only you and people you add, people you invite, or anyone signed in taking timed turns.
- In your dashboard (Robot & API), put the robot's id (
rob_…). Its live panel shows under your stream: everyone sees its camera and readouts, and anyone allowed to drive presses Drive.
The binding is PUT /api/streams/channel/:username/bot with { "robot_id": "rob_…" } (or null to unbind), the channel owner only.
Chat commands
| Command | What the robot gets |
!forward / !backward | a short drive pulse forward or back |
!left / !right | a short turn |
!headup / !headdown, !liftup / !liftdown | the head or lift, on robots that have one (Cozmo) |
!say <text> | speech, on robots that speak |
Each command goes to OpenVibe.Bot as the viewer who typed it, so the robot's rules decide: signed-in viewers only, the owner's access policy and turn queue, per-person cooldowns, and nothing while the e-stop is latched. A refusal is answered in chat ("It is not your turn at the robot yet").
Building for robots
Drivers, robot profiles, the device protocol, the REST API with the same gate, and a simulated rover to try it all are on OpenVibe.Bot: Build for OpenVibe.Bot · try the panel · supported models.
TTS & AUDIO SYSTEM
Text-to-Speech
TTS reads chat messages, donation alerts, and custom text aloud on stream. Viewers hear it in the broadcast audio mix. Streamers configure which voice, volume, rate, and message types trigger TTS.
| Method | Path | Description |
| GET | /api/tts/settings | Current TTS config (voice, volume, rate, filters) |
| PUT | /api/tts/settings | Update TTS config |
| GET | /api/tts/voices | List available TTS voices |
| POST | /api/tts/speak | Queue a message for TTS { text, voice?, rate? } |
| POST | /api/tts/skip | Skip current TTS message |
TTS Settings Schema
{
"tts_enabled": true,
"tts_voice": "en-US-Standard-D",
"tts_volume": 0.8,
"tts_rate": 1.0,
"tts_read_chat": true,
"tts_read_username": true,
"tts_min_bits": 0,
"tts_max_length": 200,
"tts_filter_links": true,
"tts_filter_emotes": false
}
101Soundboards Integration
Viewers paste 101soundboards.com URLs in chat to play sound effects. The server fetches, caches, and routes audio through the TTS queue with optional pitch/speed modifiers.
Chat Syntax
# Basic — just paste the URL
https://www.101soundboards.com/sounds/12345
# With pitch modifier (cents — 100 = one semitone up)
https://www.101soundboards.com/sounds/12345 100p
# Negative pitch (down)
https://www.101soundboards.com/sounds/12345 -200p
# With speed modifier (1.0 = normal, 0.5-3.0 range)
https://www.101soundboards.com/sounds/12345 2.5
# Both pitch and speed
https://www.101soundboards.com/sounds/12345 150p 2
Channel Soundboard Settings
| Setting | Type | Default | Description |
| soundboard_enabled | boolean | true | Enable/disable soundboard in this channel |
| soundboard_allow_pitch | boolean | true | Allow pitch modifiers |
| soundboard_allow_speed | boolean | true | Allow speed modifiers |
| soundboard_banned_ids | string | "" | Comma-separated list of banned sound IDs |
Rate Limits
| Scope | Limit |
| Per user | One sound every 8 seconds |
| Per stream | 15 sounds per minute |
Audio Queue
TTS and soundboard audio share a single playback queue. Messages play in FIFO order. The audio element routes through a GainNode for volume control. Soundboard clips support playbackRate for pitch/speed modification.
INTEGRATIONS — DISCORD, PUSH, WEBHOOKS
Discord Bot (via openvibe.network)
The Discord bot runs on openvibe.network (not OpenVibe.Live). When a stream goes live, OpenVibe.Live fires an internal event, and the bot posts an embed in the configured alerts channel.
| Feature | Description |
| Live alerts | Rich embed with stream title, thumbnail, streamer, and link |
| Dedupe | Per-streamer cooldown (configurable, default 15 min) prevents spam |
| System alerts | Admin-triggered system notifications to a separate channel |
| Account linking | Users can link their Discord account via OAuth2 on openvibe.network |
Discord Admin API (openvibe.network)
| Method | Path | Description |
| GET | /api/admin/discord | Bot status + config (tokens masked) |
| PUT | /api/admin/discord | Update bot/OAuth settings |
| POST | /api/admin/discord/test | Send test system alert |
| POST | /api/admin/discord/test-live | Send test live alert |
| POST | /api/admin/discord/reinit | Reconnect Discord bot |
Discord Account Linking
| Method | Path | Description |
| GET | /api/auth/discord/link | Initiate Discord OAuth2 (redirects to Discord) |
| GET | /api/auth/discord/callback | OAuth callback (exchanges code, links account) |
| DELETE | /api/auth/discord/link | Unlink Discord account |
| GET | /api/auth/discord/status | Check if Discord is linked |
Web Push Notifications
Push notifications use VAPID (Web Push API). Users subscribe via their browser, and receive alerts for stream go-live events.
| Method | Path | Description |
| GET | /api/push/vapid-key | Get VAPID public key for subscription |
| POST | /api/push/subscribe | Register push subscription { endpoint, keys } |
| DELETE | /api/push/unsubscribe | Remove subscription |
| GET | /api/push/status | Subscription count + notification preferences |
| PUT | /api/push/live-preferences | Toggle "all live" vs "followed only" notifications |
Notification Preferences
| Preference | Values | Description |
| stream (per-follow) | enabled/disabled | Get notified when a followed streamer goes live |
| stream_live_all | enabled/disabled | Get notified when ANY streamer goes live |
Internal Events API (server-to-server)
OpenVibe.Live emits events to openvibe.network through its internal API, with a service token scoped to the capability of each call.
| Method | Path | Description |
| POST | /internal/push-single | Send push to one user { userId, title, body, url } |
| POST | /internal/push-bulk | Send push to multiple users { userIds[], title, body, url } |
Go-live alerts
When a stream starts, OpenVibe.Live publishes live.stream.started through OpenVibe.Events, and openvibe.network tells the streamer's followers: inbox, push, email and the Discord alert. Live makes no direct call.
Multistream / Restream — /api/restream
Relay a live stream out to YouTube / Twitch / Kick / custom RTMP. Manage destinations per slot (max 10); stream keys are masked on read. All requireAuth.
| Method | Path | Notes |
| GET | /api/restream/presets | Platform + quality/encoder preset metadata |
| GET / POST | /api/restream/destinations | List / create a destination (platform, stream_key, quality_preset, managed_stream_id?, chat_relay?). Listing is per slot: ?managed_stream_id=N lists that slot, no slot lists only unbound destinations, ?all=1 lists the whole account. A stream restreams only to its own slot’s destinations |
| PUT / DELETE | /api/restream/destinations/:id | Update / delete (stops any active relay) |
| POST | /api/restream/destinations/:id/start · /stop | Start / stop the FFmpeg relay to a destination |
| GET | /api/restream/status · /viewer-counts | Per-stream relay status / cached external viewer counts (?managed_stream_id=N for one slot; default: the slots live now) |
| GET | /api/restream/oauth/:platform/start · /status | Link a platform account to auto-fill ingest/key (Twitch/YouTube; Kick links identity only) |
RobotStreamer — /api/robotstreamer
Publish a server-ingested stream to a RobotStreamer robot and (optionally) mirror its chat. Config is per stream slot only: a slot without its own config does not restream to RobotStreamer, and every call except GET needs managed_stream_id. All requireAuth.
| Method | Path | Notes |
| GET | /api/robotstreamer/integration | Load a slot's RS settings + available robots (?managed_stream_id; without it only the server's passthrough capability, exists:false) |
| POST | /api/robotstreamer/integration/validate · /login | Validate token+robot / log in with RS credentials to auto-fetch token+robots |
| PUT / DELETE | /api/robotstreamer/integration | Save config (starts publish + optional chat bridge on live streams) / remove |
PowerChat — /api/powerchat
Two-way bridge with PowerChat (alerts/overlays). OpenVibe.Live chat + viewer counts + point redemptions flow into PowerChat's overlays; signed webhooks flow out — a PowerChat donation.completed is mapped onto the Vibes donation pipeline (PowerChat cents = bucks 1:1) and credits the active goal.
| Method | Path | Notes |
| GET | /api/powerchat/status | Connection + granted scopes |
| GET | /api/powerchat/oauth/start · /callback | Connect a PowerChat account (OAuth2) |
| DELETE | /api/powerchat/oauth/connection | Disconnect |
| GET | /api/powerchat/tip-link | Attribution deep link to the streamer's tip page (?ref or ?goal_id) |
| POST | /api/powerchat/test-tip · /test-alert | Fire a simulated tip / overlay test-alert |
| POST | /api/powerchat/webhook | Signed HMAC receiver (sha256 over <ts>.<body>, ≤15-min skew, deduped) |
7. VODS, CLIPS & PASTES
Recording mode is per slot: vod (kept + browsable), clips_only (ephemeral — recorded only to source live clips, deleted on stream end), or none. RTMP streams record losslessly to fragmented .mp4 (H.264/AAC); WebRTC/jsmpeg record to .webm. New-VOD and new-clip visibility follow the slot's default_vod_visibility / default_clip_visibility.
VODs — /api/vods
| Method | Path | Auth | Notes |
| GET | /api/vods | Opt | List public VODs (?limit,offset,username,sort) (+ my private when logged in) |
| GET | /api/vods/mine | Yes | All my VODs |
| GET | /api/vods/:id | Opt | VOD detail |
| PUT | /api/vods/:id | Owner/mod | Edit title, description, visibility (public|unlisted|private) |
| DELETE | /api/vods/:id | Owner/mod | Delete VOD + media (local + B2/R2) |
| POST | /api/vods/bulk | Yes | Bulk delete|public|unlisted|private ({ ids[], action }) |
| GET | /api/vods/:id/file | No | 302 to the recording on openvibe.media (/v/:id, range requests; Media applies its visibility) |
| POST | /api/vods/clips | Yes | Create clip. From an uploaded blob, a live server-cut (stream_id, live:true, duration, at), or a VOD cut (vod_id, start_time, end_time) + title? |
Clips — /api/clips
| Method | Path | Auth | Notes |
| GET | /api/clips | No | List public clips (?limit,offset,username,sort) |
| GET | /api/clips/mine · /my-stream | Yes | Clips I made / clips others took of my streams |
| GET | /api/clips/:id | Opt | Clip detail (tracks a view) |
| GET | /api/clips/:id/file | No | 302 to the clip on openvibe.media (/c/:id) |
| PUT | /api/clips/:id/title · /visibility | Yes | Rename / set visibility |
| DELETE | /api/clips/:id | Streamer/mod | Delete clip (creator only if channel opted in) |
Comments — /api/comments (on VODs & clips)
| Method | Path | Auth | Notes |
| GET | /api/comments/:type/:id | Opt | List comments + replies, newest first (type = vod|clip, ?limit,offset) → { comments, total, thread? }; thread.url is the same thread on OpenVibe.Community (public and unlisted items) |
| GET | /api/comments/:commentId/replies | Opt | All replies of a comment |
| POST | /api/comments/:type/:id | Yes | Add a comment/reply (message, parent_id?) |
| PUT | /api/comments/:commentId | Author | Edit your own comment |
| DELETE | /api/comments/:commentId | Author/owner/staff | Delete (the VOD's or clip's owners and staff may delete any comment on it) |
Comments are OpenVibe.Community comment threads: the same thread shows on Community's page for it. When Community cannot be reached these routes answer 503 with code: "comments_unavailable".
Pastes — /api/pastes
| Method | Path | Auth | Notes |
| GET | /api/pastes | Opt | List public pastes (?limit,offset,type,username,search,sort) |
| GET | /api/pastes/:slug · /:slug/raw | Opt/No | Get a paste / raw content |
| POST | /api/pastes | Opt | Create text paste (content + title, language, visibility); anon allowed if enabled → { paste, url } |
| POST | /api/pastes/screenshot | Opt | Upload an image paste |
| PUT / DELETE | /api/pastes/:slug | Owner/staff | Edit / delete a paste |
Live Frame API — openvibe.media/live/:selector/frame.jpg
Grab a near-realtime JPEG frame of any actively-live stream slot — built for external APIs, bots, dashboards, and dev tooling. No auth, CORS-open (Access-Control-Allow-Origin: *), so you can hotlink it in an <img>, poll it from a script, or draw it onto a canvas.
| Selector | Example | Resolves to |
| Slot id | /live/1/frame.jpg | That managed-stream slot (id from your Stream Manager) |
| Slot slug | /live/whip/frame.jpg | The currently-live slot with that slug |
| @username | /live/@Goosely/frame.jpg | That streamer's top-viewed currently-live slot |
?w=64..1920 scales the width (aspect kept) · responses are cached 5s per slot — that cache is the rate limit, so poll as fast as you like · frames are cut ~2s behind the live edge. Offline? You get a styled "OFFLINE" card image with a 404 status — embeds keep looking good, scripts key off the status code (or add ?format=json for JSON errors). 503 = live but no frame could be cut (retry next window).
# Is Goosely live? Grab a frame if so
curl -f -o now.jpg https://openvibe.media/live/@Goosely/frame.jpg && echo LIVE || echo OFFLINE
# 320px-wide preview for a status page — shows the OFFLINE card automatically when not live
<img src="https://openvibe.media/live/whip/frame.jpg?w=320">
Transcript & AI Timeline API — openvibe.media/live/:selector/transcript.json
The full audio transcription log and AI overview timeline for a stream slot or streamer — same selector grammar as the frame API, JSON out, no auth, CORS-open, cached 30s (that cache is the rate limit).
| Selector | Example | Scope |
| Slot id / slug | /live/whip/transcript.json | That slot's sessions (slugs resolve while live; ids always work) |
| @username | /live/@Goosely/transcript.json | All that streamer's sessions across slots — works while offline too |
Response: { live, current, sessions[], streamer, user } — current is the in-progress recording when live; each session carries ai_overview, transcript (plain-text audio transcription), duration_seconds, created_at; streamer holds the AI streamer overview plus recent stream-memory moments (the AI timeline). ?limit=1..50 sessions (default 10).
VOD Transcript API — openvibe.media/v/:vodId/transcript.json
The same transcription + AI overview data for one existing VOD id: { vod_id, title, duration_seconds, ai_overview, transcript, ai_analyzed_at }. Private VODs return 404. Cached 30s, CORS-open.
Chat Insight API — openvibe.media/live/:user/chat-insight.json
A user's chat-related AI insight and timeline to feed bots/dashboards: :user is @username or a numeric user id. Returns { user, chat_insight, streamer } — chat_insight has today-vs-all-time chat overviews, condensed memory, and an event timeline; streamer adds the streamer overview + stream memories when they also stream. Cached 30s, CORS-open.
# What has Goosely's stream been about? (overviews + transcripts)
curl -s https://openvibe.media/live/@Goosely/transcript.json | jq ".sessions[0].ai_overview"
# Transcript of one specific VOD
curl -s https://openvibe.media/v/2028/transcript.json | jq ".transcript"
# Feed a chatter's AI insight into your bot
curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insight"
8. API REFERENCE — EVERYTHING ELSE
Chat REST — /api/chat
| Method | Path | Auth | Notes |
| POST | /api/chat/send | Yes | Post a message to global chat (bots; API tokens need the chat scope) |
| GET | /api/chat/:streamId/history | Opt | Per-streamer history (all of the streamer's slots, plus offline messages) |
| GET | /api/chat/global/history | Opt | Global chat history |
| GET | /api/chat/:streamId/replay | Opt | Chat replay for VOD/clip sync (?from,to) |
| GET | /api/chat/anon/:anonId · /relay-user/:platform/:username | Opt | Anon / relay chatter meta (+ /logs, auth) |
| GET | /api/chat/gif/{search,trending,providers} | Opt | GIF picker proxy (Tenor/Giphy) |
Emotes — /api/emotes (served by OpenVibe.Chat)
| Method | Path | Auth | Notes |
| GET | /api/emotes/all/:streamId | Opt | All emotes for a context: { defaults, channel[], global[], ffz[], bttv[], '7tv'[] }. Use ?channel=<username> to resolve a channel without a live stream (streamId 0) |
| POST | /api/emotes | Yes | Upload custom emote (multipart image, code 2–32) |
| GET | /api/emotes/mine · /channel/:userId · /global | Yes/No | My / a channel's / global custom emotes |
| GET / PUT | /api/emotes/sources | Yes | My channel's emote-source prefs (defaults/custom/ffz/bttv/7tv) |
| DELETE | /api/emotes/:id | Yes | Delete an emote |
Sounds — /api/sounds (viewer !command sound clips)
| Method | Path | Auth | Notes |
| GET | /api/sounds/all/:streamId · /channel/:userId | No | Sounds available for a stream / channel |
| POST | /api/sounds | Yes | Upload a sound (multipart sound, command 2–24) |
| GET / POST / DELETE | /api/sounds/alert/:kind | Yes | Streamer alert sounds (donation|goal) |
Direct Messages — /api/dm
| Method | Path | Auth | Notes |
| GET / POST | /api/dm/conversations | Yes | List / create a 1:1 or group conversation |
| GET / POST | /api/dm/conversations/:id/messages | Yes | Paginated messages / send (delivers over WS + push) |
| GET | /api/dm/unread | Yes | Total unread count |
| POST / DELETE / GET | /api/dm/blocks[/:userId] | Yes | Block / unblock / list |
Analytics — /api/analytics
| Method | Path | Auth | Notes |
| GET | /api/analytics/channel/:username | Opt | Public channel summary (?days ≤ 365, no revenue) |
| GET | /api/analytics/stream/:id | No | Single-stream analytics + viewer/chat chart |
| GET | /api/analytics/channel/:username/dashboard | Owner | Detailed dashboard (watch minutes, coins, top chatters) |
Media queue, News, Home & utility
| Method | Path | Auth | Notes |
| GET | /api/media/channel/:username | Opt | Media-request queue state; POST /api/media/request adds a request (spends coins) |
| GET / PUT | /api/news/my-settings | Yes | Per-channel news on/off (admin manages sources at /api/news/sources) |
| GET | /api/home/hero | No | Landing hero: { stats, media[], slogans } |
| GET | /api/thumbnails/:filename | No | Serve a thumbnail (POST /live/:streamId uploads a live thumb) |
| GET | /api/themes · /themes/me | No/Yes | List community themes / my active theme (themes tie into openvibe.network SSO) |
| GET | /api/meta/link-preview?url= | No | Link preview / OEmbed data |
| GET | /api/health · /updates | No | Health check / recent changelog (git commits) |
Rate Limits
| Scope | Window | Limit |
| GET / HEAD requests | 1 minute | 900 |
| POST / PUT / DELETE | 1 minute | 180 |
| Auth (login/register) | 15 minutes | 20 |
| File uploads | 15 minutes | 40 |
| Chat messages | per message | 1000ms default; slowmode configurable |
| Control commands | per button | cooldown_ms set per button |
| key_down / key_up events | per event | 100ms minimum |
OpenVibe.Live is open source. Contribute at github.com/OpenVibers/OpenVibe.Live. Self-host it, fork it, vibe code on top of it.