Live Now
0No one is streaming right now
Be the first to go live!
Happening Now
AI Moments
wild seconds the AI clipped from recent streamsStreaming the way it should be.
OpenVibe.Live is a living platform built by one person who actually streams here, hangs out in chat, and ships your ideas. No boardroom, no algorithm games, no mystery bans — just an epic streaming stack packed with things the big sites flat-out don't have.
That same sub-second WebRTC pipeline isn't just for talking heads. Viewers can drive robots, pan cameras, and operate interactive rigs in real time — latency low enough to actually play. Ultra-low-latency streaming built for robotics, PTZ cameras, and interactive apps. Good luck doing that on Twitch.
OpenVibe.Live is built & maintained by one hobbyist programmer who's usually right here in chat. Laid-back rules, no corporate nonsense, and a real person who actually wants your feedback. Got an idea? Say it in a stream — features here get shipped because a user asked for them. You help shape OpenVibe.Live and the whole OpenVibe. ♥
The goal is not maximum revenue; it’s long-term sustainability. Servers, storage, bandwidth, and development all cost money — so the plan is to keep the network transparent, fund operations responsibly, and formalize it as a real not-for-profit / public-benefit project once it can stand on its own.
Recently Online
No recent streams
Recent VODs
View AllRecent Clips
View AllRecent Pastes
View AllScraplandia Leaderboards
Play GameCommunity Canvas
Open CanvasThe OpenVibe Network
One account, 14 services — streaming, games, maps, food, image tools, audio tools, text generators, logo makers, PDF & document tools, network diagnostics, developer tools, video downloads, and more.
Recent Changes
Redirecting to channel...
Videos
Clips
Connecting to stream...
Switching stream…
Channel
username LiveStream Title
No rewards configured for this channel
Channel
username
No rewards configured for this channel
No videos yet
All clips created of this streamer — by anyone who clipped their streams.
No clips yet
Clips this streamer has taken — clipped from other streamers' content.
No clips yet
No pastes yet
Viewers — Latest Stream
Recent Streams
| Date | Title | Duration | Peak | Avg | Chat |
|---|
This video is private
Only the uploader can watch this video
Chat Replay
Chat messages will appear here as the video plays
Video
Create Clip
Clips from this stream
Chat Replay
Chat messages will appear here as the clip plays
Comments
Streamer Dashboard
Chat Overlay
Use this as a Browser Source in OBS to show your live chat on stream. This global URL shows chat from any of your live stream slots.
Per-slot overlay URLs are available in each stream slot's Settings tab on the Broadcast page.
Browser Source size: 800×600 is a good starting point. Background stays transparent.
Interactive Controls
Clone Preset Control Profile
Choose a preset profile to clone. You can edit the name and description before creating your own copy.
Editing: Config
Control Settings
Command rate limits are per-button (set each control's cooldown in its profile). A high global burst cap prevents floods automatically.
Control Whitelist
Hardware Bridge Scripts
Download Python scripts to connect hardware (robots, GPIO, servos) to your control profiles. Each script is pre-configured with your stream key and the buttons from the selected profile.
API Tokens
Create long-lived API tokens for bots and integrations. Tokens can authenticate against the REST API and authenticated WebSocket integrations without an OAuth login.
Chat Logs
View and manage chat messages from your streams. Search, filter, and purge messages by time range.
Camera Controls (ONVIF PTZ)
Add ONVIF-compatible cameras (Hikvision, Axis, Dahua, etc.) for pan/tilt/zoom viewer control.
Donation Goals
PowerChat Tips real donations
Connect your PowerChat account to take real tips. Confirmed donations fire your alert sound, credit your donation goals, and pop a chat celebration — automatically.
AI Chat Viewers a chat that remembers
Give your channel a persistent cast of AI viewers. They react to what's actually happening on stream, remember regulars across sessions, and each keeps their own personality and memory. Right-click anyone in chat to AI Clone them into a bot. Runs on OpenVibe.Live's shared AI up to your daily budget — or bring your own key for no cap.
Loading…
Loading…
Your channel's persistent bots. Ambient bots are generated automatically; clones come from real chatters. Personalities and memories persist across every stream.
Loading roster…
My Videos
Videos are public by default. You can set individual videos to private, or bulk-manage older media below.
My Clips
Clips you've taken from other streams. New clips are unlisted until the streamer makes them public.
Clips of My Stream
Clips viewers have taken from your streams. You can publish or delete them.
My Pastes
Text, code, and screenshots you've shared.
Cashout
Only Vibes sent to you land here — the only balance you can cash out. Or recycle it into your spendable balance to give back to the community.
Channel Points
Viewers earn your channel points by watching and chatting, then spend them on the rewards you create below. Name them whatever you like.
The bonus chest shows viewers a clickable chest on a timer for extra points.
Rewards
Create rewards your viewers can redeem with your channel points — TTS, sound effects, chat highlights, streamer challenges, and more.
Redemption Queue
Viewers who redeemed rewards. Fulfill or reject them.
Profile
Offline Screen
What viewers see in the video area when you're offline. Your chat stays open below it either way.
Images (PNG/JPG/WebP) or video/GIF (MP4/WebM/MOV/GIF) — video is auto-converted to an optimized muted loop.
Runs sandboxed (isolated from the site). Great for a links library, embeds, or animated flair.
Admin Panel
Go Live
Select a stream slot to configure your profile and go live.
Loading…
Select a stream slot from the sidebar to configure and go live
RTMP Stream Setup
Your stream is created! Paste the info below into your streaming software and hit "Start Streaming".
rtmp://localhost:1935/live
loading...
OBS Studio Setup
- Open OBS → Settings → Stream
- Set Service to Custom
- Paste the Server URL above
- Paste the Stream Key
- Click "Start Streaming" in OBS
IRL Pro / Mobile Apps Mobile
Stream from your phone using IRL Pro, Larix, Prism Live, or any RTMP-capable mobile app.
Option A — Separate URL + Key fields
Use this if your app has separate "Server URL" and "Stream Key" fields (like OBS):
- In the RTMP URL / Server field, paste:
- In the Stream Key field, paste:
Option B — Single combined URL
Some apps only have one URL field. Paste the full URL with the key included:
loading...
Username & Password: Leave blank!
OpenVibe.Live uses your stream key for authentication — no username or password. If your app asks for credentials, leave both fields empty. The stream key in the URL is all you need.
IRL Pro Step-by-Step
- Open IRL Pro → ⚙️ Settings → Streaming
- Select Platform → Custom RTMP
- Paste the Server URL and Stream Key from Option A above
- Leave Username and Password empty
- Set Resolution to 720p, Bitrate to 2500–4000 kbps
- Go back and tap Go Live
Tips:
- Enable SRTLA Bonding in IRL Pro for better mobile reliability — combines WiFi + cellular
- Set the codec to H.264 for best compatibility
- If you see buffering, lower your bitrate to 1500–2000 kbps
- IRL Pro's chat overlay can be pointed at
https://openvibe.live/embed/chat/your-stream-idfor live chat on screen
FFmpeg CLI (No OBS) Advanced
Stream directly from a terminal — great for headless servers, Raspberry Pi, 3D printer cams, and automated pipelines.
loading...
loading...
loading...
24/7 Tips:
- Wrap in
while true; do ffmpeg ...; sleep 5; donefor auto-restart on disconnect - Use a
systemdservice ortmux/screensession for unattended streams - For IP cameras via RTSP, replace
-f v4l2 -i /dev/video0with-rtsp_transport tcp -i rtsp://camera-ip:554/stream -tune zerolatencyminimizes delay; remove it for 24/7 streams where latency doesn't matter
JSMPEG Stream Setup
Your stream is created! Copy one of the FFmpeg commands below and run it in your terminal. Ideal for Raspberry Pi, embedded devices, Linux desktops, and headless servers.
loading...
loading...
loading...
loading...
loading...
loading...
Quick Start
- Install FFmpeg:
sudo apt install ffmpeg(Debian/Ubuntu) orbrew install ffmpeg(macOS) - Copy a command above and paste it in your terminal
- Adjust the device paths if needed (see device discovery below)
- Press Enter — your stream will appear on your channel page
Finding Your Devices
Camera / Video
v4l2-ctl --list-devices
v4l2-ctl -d /dev/video0 --list-formats-ext
ffmpeg -f avfoundation -list_devices true -i ""
ffmpeg -f v4l2 -i /dev/video0 -frames 1 test.jpg && xdg-open test.jpg
Audio / Microphone
arecord -l
pactl list short sources
arecord -d 5 -f cd test.wav && aplay test.wav
Replace: -f alsa -i default → -f pulse -i default
Special Inputs
Start OBS → Tools → Start Virtual Camera → use /dev/video2 (or check v4l2-ctl --list-devices)
libcamera-vid -t 0 --inline --codec mjpeg -o - | ffmpeg -i pipe:0 -f mpegts -codec:v mpeg1video -s 640x480 -b:v 350k -bf 0 http://SERVER:PORT/KEY/640/480/
FFmpeg Tuning Reference
| Flag | What it does | Example |
|---|---|---|
-s | Resolution | -s 1280x720 |
-b:v | Video bitrate | -b:v 800k |
-r | Frame rate | -r 30 |
-b:a | Audio bitrate | -b:a 128k |
-ar | Audio sample rate | -ar 44100 |
-ac | Audio channels (1=mono, 2=stereo) | -ac 1 |
-bf 0 | Disable B-frames (required for JSMPEG) | -bf 0 |
-q:v | Quality (2=best, 31=worst) | -q:v 5 |
-muxdelay | Lower = less latency | -muxdelay 0.001 |
JSMPEG requires mpeg1video codec and mp2 audio. H.264/AAC will not work. -bf 0 is mandatory.
Recommended Presets
| Use Case | Resolution | Video Bitrate | Audio |
|---|---|---|---|
| Raspberry Pi Zero | 320x240 | 200k | 64k mono |
| Raspberry Pi 3/4 | 640x480 | 350–500k | 128k mono |
| Desktop / Laptop | 1280x720 | 800k–1.5M | 128k stereo |
| High Quality | 1920x1080 | 2–4M | 192k stereo |
Troubleshooting
- No video device: Check
ls /dev/video*— if empty, your camera isn't connected or the driver isn't loaded. Trysudo modprobe uvcvideo. - No audio device: Run
arecord -l— if empty, plug in a USB mic or checkpulseaudio --check. Use-f pulse -i defaultif using PipeWire or PulseAudio. - Device busy: Another app is using the camera. Close OBS, Cheese, or any other camera app and retry.
- Connection refused: Make sure the server is running and the port is reachable. Check firewalls:
sudo ufw allow PORT. - High latency: Lower the resolution, reduce bitrate, and ensure
-muxdelay 0.001is set. - Choppy video: Your upload bandwidth may be too low for the bitrate. Reduce
-b:vor resolution. - Permission denied on camera: Add yourself to the video group:
sudo usermod -aG video $USERthen log out and back in. - macOS: Use
-f avfoundation -i "0:0"instead of-f v4l2 -i /dev/video0. Device indices fromffmpeg -f avfoundation -list_devices true -i "".
3D Printer & 24/7 Streams
For unattended, always-on streams (3D printers, security cameras, aquariums, etc.) — optimize for stability over latency.
loading...
loading...
Auto-Restart with systemd
Create a service file so your stream starts on boot and auto-restarts on failure:
[Unit]
Description=OpenVibe.Live JSMPEG Feed
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/bin/ffmpeg -f v4l2 -framerate 10 -video_size 640x480 -i /dev/video0 -f mpegts -codec:v mpeg1video -s 640x480 -b:v 250k -bf 0 -muxdelay 0.001 http://YOUR_SERVER:PORT/KEY/640/480/
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload && sudo systemctl enable --now openvibe-stream
Tips:
- 10fps is plenty for 3D printers — saves bandwidth and CPU
- Use
-b:v 150kto-b:v 300kdepending on resolution and motion - For Raspberry Pi, prefer 320x240 or 640x480 to reduce CPU load
- Shell loop alternative:
while true; do ffmpeg ...; sleep 5; done
WebRTC via OBS (WHIP)
Your stream is created! Paste the info below into OBS Studio 30+ to start streaming with ultra-low latency via WebRTC. This is newer and faster than RTMP.
Note: on some Linux distributions, the system OBS package may disable WHIP even in OBS 30+. If you do not see the WHIP service in Settings → Stream, install OBS from Flatpak or AppImage instead of the distro package.
loading...
hidden
The bearer token is hidden by default to prevent accidental disclosure. Tap Show only when you need it, and paste only the raw JWT string into OBS — do not include the word Bearer.
OBS 30+ Setup
- Open OBS → Settings → Stream
- Set Service to WHIP
- Paste the WHIP Endpoint URL above
- Paste the Bearer Token as the auth key
- Click "Start Streaming" in OBS
Requires OBS Studio 30.0 or newer. Older versions do not support WHIP. If the WHIP option is missing in OBS, use the Flatpak or AppImage build, or switch to RTMP.
Manage restream destinations in the stream slot workspace.
Loading...
Change profile without ending the stream. Viewers see the new buttons immediately.
Create Clip
Mark a start and end time from your stream (max 60 seconds). The clip is extracted from the server-side recording.
Loading…
Change profile without ending the stream. Viewers see the new buttons immediately.
Manage restream destinations in the stream slot workspace. Destinations with Auto-Start will begin automatically when you go live.
No rewards configured for this channel
Switch Camera
Pick the exact camera you want to use live.
Voice Channels
Global Chat
Live messages from all streams and the lobby
Global Chat — AI Insight
Timeline
Community Memory
Create Voice Channel
Theme Directory
Browse and download themes created by the community.
Updates
Recent changes and patch notes from the GitHub repository.
Pastes & Images
Share code, notes, prompts, and images with the community.
OpenVibe.Live API Docs
Modular docs for vibe coders. Copy just the section you need, or grab everything.
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 | SQLite (better-sqlite3) | WAL mode, foreign keys |
| 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, or ?token=JWT query param on WebSocket URLs.
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 │
│ ├─ /api/controls — Interactive control profiles │
│ ├─ /ws/chat — Real-time chat │
│ ├─ /ws/broadcast — WebRTC signaling │
│ ├─ /ws/control — Hardware command relay │
│ ├─ /whip/:id — WHIP WebRTC ingestion │
│ └─ RTMP server — rtmp://host/live │
└──────────────────────────────────────────────────────┘
Event Flow: Stream Goes Live
1. Streamer calls POST /api/streams (OpenVibe.Live)
2. OpenVibe.Live creates stream record, starts media endpoint
3. OpenVibe.Live calls POST /internal/events/stream-live (openvibe.network)
4. openvibe.network Discord bot sends embed to #alerts channel (with dedupe)
5. openvibe.network creates STREAM_LIVE notifications for:
- All followers of the streamer
- Users opted into "all live" alerts
6. Push notifications fire via Web Push (VAPID)
7. Fallback: OpenVibe.Live sends direct webhook if openvibe.network unreachable
WebSocket Services
| Path | Purpose | Auth |
|---|---|---|
| /ws/chat | Chat rooms (per-stream or global) | Optional (anon allowed) |
| /ws/broadcast | WebRTC SFU signaling | JWT required |
| /ws/control | Interactive controls relay (viewer ↔ hardware) | JWT or API key |
| /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/*
Protected by X-Internal-Key header for server-to-server calls.
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> (also read from the ov_token cookie, or a ?token= query param on WebSocket URLs). 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, email, profile_color |
| 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
browserorwhip) — 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, allowP2pFallback | 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 |
Legacy offer / answer / ice-candidate relay messages are disabled by default. They are only honored when ALLOW_P2P_FALLBACK=true is explicitly set for emergency rollback.
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&token=JWT
Both params are optional: omit stream for global chat; omit token to connect anonymously (assigned anonXXXXX identity).
Client → Server Messages
| Type | Payload | Description |
|---|---|---|
| chat | { message, reply_to_id?, auto_delete_minutes? } | Send a message (max 500 chars) |
| join_stream | { streamId } | Join a specific stream's chat room |
| 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. First message after connect. |
| 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 WS_URL = 'wss://openvibe.live/ws/chat?stream=123&token=YOUR_JWT';
const ws = new WebSocket(WS_URL);
ws.on('open', () => console.log('Bot connected'));
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 = 'YOUR_JWT_TOKEN'
STREAM_ID = '123'
WS_URL = f'wss://openvibe.live/ws/chat?stream={STREAM_ID}&token={TOKEN}'
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')
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, 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 | Send message via REST (fallback) |
| 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 · /activate · /deactivate | 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
Rolling LLM summaries produced by the chat-AI poller: a global "community vibe", plus per-user / per-anon / per-relay-user insight (overview + running memory + a short timeline). No auth required to read.
| Method | Path | Notes |
|---|---|---|
| GET | /api/chat-ai/global | Site-wide chat overview + timeline + memory |
| GET | /api/chat-ai/user/:id | A user's "today vs all-time" insight (+ streamer overview 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 |
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. INTERACTIVE CONTROL SYSTEM — ROBOT/HARDWARE GUIDE
The control system lets streamers expose interactive buttons that viewers press to control physical hardware (robots, cameras, servo rigs, etc.). Three interaction types: single-press buttons, keyboard hold (continuous drive), and video-click (x/y coordinates on the live video feed).
Control Config Profiles
Streamers create reusable control configs. Each config holds buttons. When going live, a config is applied to the stream — its buttons get copied to that stream session.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/controls/configs | Yes | List your configs (with button count) |
| POST | /api/controls/configs | Yes | Create config { name, description } |
| GET | /api/controls/configs/:id | Yes | Config with full buttons array |
| PUT | /api/controls/configs/:id | Yes | Update name / description |
| DELETE | /api/controls/configs/:id | Yes | Delete config |
| POST | /api/controls/configs/:id/buttons | Yes | Add button to config |
| PUT | /api/controls/configs/:id/buttons/:btnId | Yes | Update button |
| DELETE | /api/controls/configs/:id/buttons/:btnId | Yes | Delete button |
| POST | /api/controls/configs/:id/activate | Yes | Set as channel default config |
| POST | /api/controls/configs/deactivate | Yes | Clear channel default |
| POST | /api/controls/configs/:id/apply/:streamId | Yes | Copy config buttons onto a live stream |
| GET | /api/controls/configs/:id/bridge-script?type=generic|cozmo | Yes | Download a Python hardware bridge script pre-configured with the profile's buttons |
Button Schema
{
"label": "Forward", // Display name shown to viewers (max 50)
"command": "forward", // String sent to your hardware bridge (max 100)
"icon": "fa-arrow-up", // FontAwesome icon class (without fa-solid prefix)
"control_type": "keyboard", // "button" | "keyboard" | "toggle" | "dpad"
"key_binding": "w", // Optional keyboard shortcut for viewers (max 20)
"cooldown_ms": 100, // Per-press cooldown in ms (0-30000)
"sort_order": 0, // Display order (ascending)
"btn_color": "#ffffff", // Button text color (CSS color — safe values only)
"btn_bg": "#1a1a2e", // Button background color
"btn_border_color": "#6366f1", // Button border color
"is_enabled": 1 // 1=shown, 0=hidden
}
Control Type Behaviour
| control_type | Viewer Interaction | Messages Sent |
|---|---|---|
| button | Single click or key tap | command |
| keyboard | Hold mouse/touch/key → continuous. Release → stop | key_down on press, key_up on release |
| toggle | Click to toggle on/off | command (on) / command (off) |
| dpad | D-pad style directional | command |
Channel Control Settings
| Method | Path | Description |
|---|---|---|
| GET | /api/controls/settings/channel | Get settings |
| PUT | /api/controls/settings/channel | Update settings |
{
"control_mode": "open", // "open" | "whitelist" | "disabled"
"anon_controls_enabled": true, // Allow anonymous viewers to use controls
"control_rate_limit_ms": 100, // Global rate limit per user (100-30000)
"video_click_enabled": true, // Enable video click input
"video_click_rate_limit_ms": 0 // Per-user minimum time between video clicks; 0 = use global rate limit
}
Per-Stream Controls (Legacy / Direct)
| Method | Path | Description |
|---|---|---|
| GET | /api/controls/:streamId | Get all controls + settings for a stream |
| POST | /api/controls/:streamId | Add a button directly to active stream |
| PUT | /api/controls/:streamId/:id | Update control |
| DELETE | /api/controls/:streamId/:id | Delete control |
| POST | /api/controls/:streamId/presets/cozmo | Apply Cozmo robot preset buttons |
6. CONTROL WEBSOCKET — HARDWARE BRIDGE
This is how your code receives viewer commands. Connect as mode=hardware and listen for messages. Your bridge translates them into robot/hardware actions.
Connection URLs
# Hardware bridge (your robot/device — receives commands)
wss://openvibe.live/ws/control?mode=hardware&stream_key=YOUR_STREAM_KEY
# Viewer (browser/bot — sends commands)
wss://openvibe.live/ws/control?mode=viewer&token=JWT&stream=STREAM_ID
Hardware Receives These Messages
| Type | Payload Fields | When |
|---|---|---|
| connected | message | Immediately on connect — confirms auth |
| command | command, control_id, from_user, timestamp | Viewer clicked a button (button/toggle/dpad type) |
| key_down | command, control_id, from_user, timestamp | Viewer started holding a keyboard-type button |
| key_up | command, control_id, from_user, timestamp | Viewer released the button |
| video_click | x (0-1), y (0-1), from_user, timestamp | Viewer clicked on the video feed. 0,0 = top-left, 1,1 = bottom-right |
Hardware Can Send
| Type | Payload | Effect |
|---|---|---|
| status | { any fields } | Relayed to all viewers as hardware_status message |
Viewer Sends These Messages
| Type | Payload | Description |
|---|---|---|
| command | { command, control_id } | Single button press |
| key_down | { command, control_id } | Start holding (keyboard type) |
| key_up | { command, control_id } | Release hold |
| video_click | { x: 0-1, y: 0-1 } | Click on video feed |
Viewer Receives These Messages
| Type | Key Fields | Description |
|---|---|---|
| controls | controls[], settings{} | Initial buttons + channel settings on connect |
| command_executed | command, by | Someone pressed a button (activity feed) |
| key_held | command, by | Someone started holding a key |
| key_released | command, by | Someone released a key |
| video_click_activity | x, y, by | Someone clicked the video |
| hardware_status | any | Status update from hardware bridge |
| error | message | Denied / not live / banned |
| cooldown | message | Rate limited |
Permission Model
| control_mode | Who can send commands |
|---|---|
| open | Any viewer (anon allowed if anon_controls_enabled=true) |
| whitelist | Stream owner + explicitly whitelisted users only |
| disabled | Nobody — controls are turned off |
Hardware Bridge — Python (Full Example)
#!/usr/bin/env python3
"""
OpenVibe.Live Hardware Bridge — generic template.
pip install websocket-client
"""
import json, time, threading, websocket
STREAM_KEY = 'YOUR_STREAM_KEY'
WS_URL = f'wss://openvibe.live/ws/control?mode=hardware&stream_key={STREAM_KEY}'
held_keys = set() # Track currently held keyboard commands
def do_command(cmd):
"""Map command strings to your hardware."""
print(f'Command: {cmd}')
# Example: call GPIO, serial, HTTP, etc.
# if cmd == 'forward': robot.drive(speed=100)
def do_start_hold(cmd):
"""Called when a keyboard-type button starts being held."""
held_keys.add(cmd)
print(f'Hold start: {cmd}')
def do_stop_hold(cmd):
"""Called when a keyboard-type button is released."""
held_keys.discard(cmd)
print(f'Hold stop: {cmd}')
if not held_keys:
print('All keys released — stop movement')
# robot.stop()
def do_video_click(x, y):
"""Called when a viewer clicks the video feed. x,y are 0-1 normalized."""
print(f'Video click at ({x:.2f}, {y:.2f})')
# Navigate toward click: x < 0.4 = turn left, x > 0.6 = turn right
# y close to 0 = far away (drive more), y close to 1 = near (drive less)
def on_message(ws, raw):
try:
msg = json.loads(raw)
t = msg.get('type')
if t == 'connected':
print('Hardware bridge authenticated!')
elif t == 'command':
do_command(msg['command'])
elif t == 'key_down':
do_start_hold(msg['command'])
elif t == 'key_up':
do_stop_hold(msg['command'])
elif t == 'video_click':
do_video_click(msg['x'], msg['y'])
except Exception as e:
print(f'Error: {e}')
def on_open(ws): print('Connected')
def on_close(ws, *a): print('Disconnected')
def on_error(ws, e): print(f'WS error: {e}')
while True:
ws = websocket.WebSocketApp(WS_URL, on_message=on_message,
on_open=on_open, on_close=on_close, on_error=on_error)
ws.run_forever(ping_interval=30)
print('Reconnecting in 5s...')
time.sleep(5)
Hardware Bridge — Node.js (Full Example)
const WebSocket = require('ws');
const STREAM_KEY = 'YOUR_STREAM_KEY';
const WS_URL = `wss://openvibe.live/ws/control?mode=hardware&stream_key=${STREAM_KEY}`;
const heldKeys = new Set();
function connect() {
const ws = new WebSocket(WS_URL);
ws.on('message', (raw) => {
const msg = JSON.parse(raw);
switch (msg.type) {
case 'connected':
console.log('Hardware bridge authenticated!');
break;
case 'command':
console.log(`Button: ${msg.command} by ${msg.from_user}`);
doCommand(msg.command);
break;
case 'key_down':
heldKeys.add(msg.command);
console.log(`Hold start: ${msg.command}`);
startContinuous(msg.command);
break;
case 'key_up':
heldKeys.delete(msg.command);
console.log(`Hold stop: ${msg.command}`);
if (heldKeys.size === 0) stopAll();
break;
case 'video_click':
console.log(`Click at (${msg.x.toFixed(2)}, ${msg.y.toFixed(2)}) by ${msg.from_user}`);
navigateToClick(msg.x, msg.y);
break;
}
});
ws.on('close', () => setTimeout(connect, 5000));
ws.on('error', (e) => console.error(e));
}
function doCommand(cmd) { /* implement hardware control */ }
function startContinuous(cmd) { /* start motors etc. */ }
function stopAll() { /* stop all motors */ }
function navigateToClick(x, y) { /* steer toward x,y */ }
connect();
API Keys (for hardware bridges without JWT)
| Method | Path | Description |
|---|---|---|
| POST | /api/controls/api-key | Generate API key (shown once — save it) |
| GET | /api/controls/api-keys | List keys (masked) |
| GET | /api/controls/cozmo-script | Download pre-built Cozmo robot bridge Python script |
Per-Profile Bridge Script Generator
Download Python bridge scripts that come pre-configured with your control profile's buttons and your stream key. Available from the Dashboard (Hardware Bridge Scripts section) or the Broadcast page (Script button next to profile selector).
| Type | Description |
|---|---|
| generic | Prints all commands to console. Edit handle_command() / handle_key_down() / handle_key_up() to control your hardware. Works with any device — GPIO, serial, HTTP, etc. |
| cozmo | Pre-mapped to pycozmo actions (drive, lift, head, animations). Includes COMMAND_MAP dict so you can remap your custom button commands to Cozmo movements. |
# Download via API:
GET /api/controls/configs/42/bridge-script?type=generic
GET /api/controls/configs/42/bridge-script?type=cozmo
# Both return a .py file download with:
# - Your stream key (auto-connects as hardware bridge)
# - All enabled buttons from the profile listed as BUTTONS
# - Skeleton handler functions ready to fill in
Hardware Status Indicator
When a hardware bridge connects, viewers see a live connection indicator:
| Indicator | Meaning |
|---|---|
| ⚫ (grey) | No controls configured for this stream |
| 🟡 (yellow) | Controls present but no hardware bridge connected |
| 🟢 (green) | Hardware bridge is online — commands will be received |
Hardware bridges send { type: "status", connected: true } on connect. The server broadcasts this to all viewers as a hardware_status message. Send a status with connected: true to turn the indicator green.
ONVIF Camera Control
Control PTZ cameras via ONVIF. Cameras appear as controls in the stream control panel.
| Method | Path | Description |
|---|---|---|
| POST | /api/onvif/discover | Auto-discover ONVIF cameras on local network |
| POST | /api/onvif/cameras | Add camera { host, port, username, password, name } |
| GET | /api/onvif/cameras | List cameras |
| PUT | /api/onvif/cameras/:id | Update camera |
| DELETE | /api/onvif/cameras/:id | Delete camera |
| GET | /api/onvif/cameras/:id/presets | List presets |
| POST | /api/onvif/cameras/:id/presets | Create preset |
ONVIF movements (send via control WS with isOnvif: true, cameraId: X, movement: Y):
pan_left, pan_right, tilt_up, tilt_down, zoom_in, zoom_out
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 via internal API. Protected by X-Internal-Key header.
| Method | Path | Description |
|---|---|---|
| POST | /internal/events/stream-live | Notify: stream went live { streamer, stream } |
| 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 } |
Fallback Webhook (OpenVibe.Live direct)
If openvibe.network is unreachable, OpenVibe.Live falls back to its built-in Discord webhook sender. Configure the webhook URL in OpenVibe.Live site settings (discord_webhook_url).
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?) |
| 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 |
| 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 slot with an account-level default. All requireAuth.
| Method | Path | Notes |
|---|---|---|
| GET | /api/robotstreamer/integration | Load RS settings + available robots (?managed_stream_id) |
| 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/file/:filename | Opt | Serve VOD/clip media (range requests) |
| 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) |
| 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 (type = vod|clip) |
| POST | /api/comments/:type/:id | Yes | Add a comment/reply (message, parent_id?) |
| PUT / DELETE | /api/comments/:commentId | Author/mod | Edit / delete |
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 | REST fallback to post a global chat message |
| GET | /api/chat/:streamId/history | Opt | Per-streamer history (all slots + offline unless ?scope=stream) |
| 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
| 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.
Comments