WebSocket API Code Example
// Connect to WebSocket API
const socket = new WebSocket("wss://api.vdo.ninja:443");
// Track connection state
socket.onopen = function() {
console.log("Connected to VDO.Ninja API");
// Join with your API ID
socket.send(JSON.stringify({ "join": "YOUR_API_ID" }));
};
// Handle messages
socket.onmessage = function(event) {
if (event.data) {
const data = JSON.parse(event.data);
console.log("Received:", data);
}
};
// Reconnection logic (crucial for production)
socket.onclose = function() {
console.log("Connection closed, attempting to reconnect...");
setTimeout(() => {
// Implement reconnection logic here
}, 1000);
};
// Example commands
function sendCommand(action, value = null, target = null) {
const msg = { action };
if (value !== null) msg.value = value;
if (target !== null) msg.target = target;
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(msg));
} else {
console.error("Socket not connected");
}
}
// Command examples:
// sendCommand("mic", "toggle");
// sendCommand("camera", false);
// sendCommand("soloVideo", "toggle");
// sendCommand("layout", 2);
// For directors - target a guest:
// sendCommand("mic", "toggle", "1"); // Target guest in slot 1
https://api.vdo.ninja/YOUR_API_ID/action/target/value
HTTP API Code Example
// HTTP GET API Example
function sendHTTPRequest(apiID, action, target = "null", value = "null") {
// Construct URL
const url = `https://api.vdo.ninja/${apiID}/${action}/${target}/${value}`;
// Send request
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.text();
})
.then(data => {
// Handle response (could be text or JSON)
try {
const jsonData = JSON.parse(data);
console.log("Response JSON:", jsonData);
} catch (e) {
console.log("Response text:", data);
}
})
.catch(error => {
console.error("Error:", error);
});
}
// Usage examples:
// sendHTTPRequest("YOUR_API_ID", "mic", "null", "toggle");
// sendHTTPRequest("YOUR_API_ID", "camera", "null", "false");
// sendHTTPRequest("YOUR_API_ID", "joinGroup", "null", "1");
// For directors targeting a specific guest
// sendHTTPRequest("YOUR_API_ID", "mic", "1", "toggle"); // Target guest in slot 1
// sendHTTPRequest("YOUR_API_ID", "addScene", "2", "1"); // Add guest 2 to scene 1
SSE provides a one-way communication channel to receive events from VDO.Ninja without sending requests.
SSE API Code Example
// Server-Sent Events (SSE) Example
function connectToSSE(apiID) {
// Create EventSource connection
const eventSource = new EventSource(`https://api.vdo.ninja/sse/${apiID}`);
// Connection opened
eventSource.onopen = function() {
console.log("SSE connection established");
};
// Listen for messages
eventSource.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
console.log("SSE event:", data);
// Handle different types of events
if (data.type === "state_update") {
// Handle state updates
updateUI(data.state);
} else if (data.type === "guest_joined") {
// Handle guest joining
console.log(`Guest joined: ${data.guestID}`);
}
// Add more event handlers as needed
} catch (e) {
console.error("Error parsing SSE event:", e);
}
};
// Handle errors
eventSource.onerror = function(error) {
console.error("SSE connection error:", error);
// Close connection
eventSource.close();
// Implement reconnection logic
setTimeout(() => {
console.log("Attempting to reconnect SSE...");
connectToSSE(apiID);
}, 3000);
};
// Return EventSource instance (for later disconnection if needed)
return eventSource;
}
// Usage:
// const sseConnection = connectToSSE("YOUR_API_ID");
//
// // To disconnect later:
// // sseConnection.close();
Although HTTP GET is the primary API method, POST can be useful for complex data or when query parameters need to be hidden.
HTTP POST API Code Example
// HTTP POST API Example
function sendPOSTRequest(apiID, data) {
// Construct URL
const url = `https://api.vdo.ninja/${apiID}`;
// Configure request
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
};
// Send request
fetch(url, options)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log("Response:", data);
})
.catch(error => {
console.error("Error:", error);
});
}
// Usage examples:
// sendPOSTRequest("YOUR_API_ID", {
// action: "mic",
// value: "toggle"
// });
//
// sendPOSTRequest("YOUR_API_ID", {
// action: "layout",
// value: [
// {"x":0,"y":0,"w":50,"h":100,"slot":0},
// {"x":50,"y":0,"w":50,"h":100,"slot":1}
// ]
// });
//
// For directors targeting a specific guest:
// sendPOSTRequest("YOUR_API_ID", {
// action: "sendDirectorChat",
// target: "1",
// value: "You're live in 5 seconds!"
// });
Remote control compatible camera hardware using pan, tilt, zoom, focus, and exposure commands.
Zoom
Focus
Pan & Tilt
Exposure
PTZ Controls Code Example
// Camera Control (PTZ) Example
function sendCameraCommand(apiID, action, value, value2 = null, target = null) {
// Using WebSocket (preferred for responsive camera control)
if (socket && socket.readyState === WebSocket.OPEN) {
const command = { action, value };
if (value2 !== null) command.value2 = value2;
if (target !== null) command.target = target;
socket.send(JSON.stringify(command));
}
// Using HTTP as fallback
else {
let url = `https://api.vdo.ninja/${apiID}/${action}`;
if (target !== null) {
url += `/${target}`;
} else {
url += `/null`;
}
url += `/${value}`;
// value2 can't be directly used in the standard HTTP API, so it's ignored here
fetch(url)
.then(response => response.text())
.then(data => console.log("Response:", data))
.catch(error => console.error("Error:", error));
}
}
// Usage examples:
// Relative adjustments
// sendCameraCommand("YOUR_API_ID", "zoom", 0.1); // Zoom in
// sendCameraCommand("YOUR_API_ID", "zoom", -0.1); // Zoom out
// sendCameraCommand("YOUR_API_ID", "focus", 0.1); // Focus farther
// sendCameraCommand("YOUR_API_ID", "pan", -0.1); // Pan left
// sendCameraCommand("YOUR_API_ID", "tilt", 0.1); // Tilt up
// Absolute adjustment (for zoom)
// sendCameraCommand("YOUR_API_ID", "zoom", 0.75, "abs"); // Set zoom to 75%
// Targeting a specific guest's camera (for directors)
// sendCameraCommand("YOUR_API_ID", "zoom", 0.1, null, "2"); // Zoom guest 2's camera in
Control VDO.Ninja using MIDI controllers with various mapping options.
No MIDI message received
Default MIDI Mappings
VDO.Ninja supports several MIDI mapping schemes, which can be configured using the midiHotkeys parameter.
- G3: Toggle chat
- A3: Toggle mic
- B3: Toggle video
- C4: Toggle screenshare
- D4: Hangup
- E4: Raise hand
- F4: Toggle recording
- G4: Director audio
- A4: Director hangup
- B4: Toggle speaker
- G1: Toggle chat
- A1: Toggle mic
- B1: Toggle video
- C2: Toggle screenshare
- D2: Hangup
- E2: Raise hand
- F2: Toggle recording
- G2: Director audio
- A2: Director hangup
- B2: Toggle speaker
Uses note C1 with different velocity values:
- C1 + velocity 0: Toggle chat
- C1 + velocity 1: Toggle mic
- C1 + velocity 2: Toggle video
- C1 + velocity 3: Toggle screenshare
- C1 + velocity 4: Hangup
- C1 + velocity 5: Raise hand
- C1 + velocity 6: Toggle recording
- C1 + velocity 7: Director audio
- C1 + velocity 8: Director hangup
- C1 + velocity 9: Toggle speaker
Use MIDI to control camera:
- C5 + velocity (0-127): Zoom (absolute)
- D5 + velocity (0-127): Focus
- E5 + velocity (0-127): Pan
- F5 + velocity (0-127): Tilt
- G5 + velocity (0-127): Exposure
MIDI API Code Example
// MIDI Integration Example
function initializeMIDI() {
// Check if Web MIDI API is supported
if (!navigator.requestMIDIAccess) {
console.error("Web MIDI API not supported in this browser");
return Promise.reject("MIDI not supported");
}
// Request MIDI access
return navigator.requestMIDIAccess({ sysex: false })
.then(midiAccess => {
console.log("MIDI Access granted");
// Get MIDI inputs
const inputs = midiAccess.inputs.values();
const devices = [];
// Setup devices
for (let input = inputs.next(); input && !input.done; input = inputs.next()) {
devices.push(input.value);
setupMIDIDevice(input.value);
}
// Listen for device connection/disconnection
midiAccess.onstatechange = function(e) {
console.log("MIDI state change:", e.port.name, e.port.state);
};
return { midiAccess, devices };
})
.catch(error => {
console.error("Failed to get MIDI access:", error);
throw error;
});
}
function setupMIDIDevice(inputDevice) {
// Set up MIDI message handler
inputDevice.onmidimessage = function(message) {
// Extract MIDI data
const command = message.data[0];
const note = message.data[1];
const velocity = message.data[2];
console.log(`MIDI message - Command: ${command}, Note: ${note}, Velocity: ${velocity}`);
// Handle Note On messages (144 = Note On, channel 1)
if (command === 144 && velocity > 0) {
handleNoteOn(note, velocity);
}
// Handle Note Off messages (128 = Note Off, channel 1)
else if (command === 128 || (command === 144 && velocity === 0)) {
handleNoteOff(note);
}
// Handle Control Change messages (176 = CC, channel 1)
else if (command === 176) {
handleControlChange(note, velocity);
}
};
}
function handleNoteOn(note, velocity) {
// Convert MIDI note number to note name (e.g. 60 = C4)
const noteName = getNoteNameFromNumber(note);
// Example mapping for midiHotkeys=1
switch (noteName) {
case "G3":
sendCommand("toggleChat");
break;
case "A3":
sendCommand("mic", "toggle");
break;
case "B3":
sendCommand("camera", "toggle");
break;
case "C4":
sendCommand("togglescreenshare");
break;
case "D4":
sendCommand("hangup");
break;
// Add more mappings as needed
}
}
function handleNoteOff(note) {
// Handle note off events if needed
}
function handleControlChange(cc, value) {
// Map control change messages to actions
switch (cc) {
case 20: // Example CC for zoom
const zoomValue = value / 127; // Normalize to 0-1 range
sendCommand("zoom", zoomValue, "abs");
break;
case 21: // Example CC for focus
const focusValue = (value - 64) / 64; // Normalize to -1 to 1 range
sendCommand("focus", focusValue);
break;
// Add more CC mappings as needed
}
}
// Helper function to convert MIDI note number to note name
function getNoteNameFromNumber(noteNumber) {
const notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
const octave = Math.floor(noteNumber / 12) - 1;
const noteName = notes[noteNumber % 12];
return noteName + octave;
}
// Usage:
// initializeMIDI()
// .then(midi => console.log("MIDI ready with devices:", midi.devices))
// .catch(error => console.error("MIDI setup failed:", error));
These controls allow directors to manage remote guests via the API.
Guest Control Code Example
// Guest Control Example (Director Mode)
function controlGuest(apiID, target, action, value = null, method = "websocket") {
if (method === "websocket") {
// WebSocket method
if (socket && socket.readyState === WebSocket.OPEN) {
const msg = {
target: target,
action: action
};
if (value !== null) {
msg.value = value;
}
socket.send(JSON.stringify(msg));
console.log("Sent guest command via WebSocket:", msg);
} else {
console.error("WebSocket not connected");
}
} else if (method === "http") {
// HTTP method
const targetStr = target || "null";
const valueStr = value !== null ? value : "null";
const url = `https://api.vdo.ninja/${apiID}/${action}/${targetStr}/${valueStr}`;
fetch(url)
.then(response => response.text())
.then(data => console.log("Guest command response:", data))
.catch(error => console.error("Error sending guest command:", error));
}
}
// Usage examples:
// controlGuest("YOUR_API_ID", "1", "mic", "toggle"); // Toggle mic for guest 1
// controlGuest("YOUR_API_ID", "2", "camera", false); // Turn off camera for guest 2
// controlGuest("YOUR_API_ID", "3", "addScene", 1); // Add guest 3 to scene 1
// controlGuest("YOUR_API_ID", "1", "sendDirectorChat", "You're live in 10 seconds");
// controlGuest("YOUR_API_ID", "2", "volume", 85); // Set guest 2 volume to 85%
// controlGuest("YOUR_API_ID", "4", "forward", "room123"); // Transfer guest 4 to room123
Complete reference for all supported API commands, parameters, and response formats.
API Overview
The VDO.Ninja API supports multiple communication methods:
- WebSocket API: Real-time bidirectional communication (preferred for most uses)
- HTTP GET API: Simple REST-style requests, compatible with hotkey systems
- HTTP POST API: For complex data structures like layout objects
- Server-Sent Events (SSE): One-way communication channel for receiving updates
- MIDI Integration: Control via MIDI devices using various mapping schemes
For integrations with BitFocus Companion, check out the official Companion module.
For custom embeds, refer to the IFrame API Documentation.
Command Reference
The following tables list all available commands, their parameters, and what they do.
Self Controls
| Action | Target | Value | Details |
|---|---|---|---|
| speaker | null | true/false/toggle | Control local speaker (audio playback) |
| mic | null | true/false/toggle | Control local microphone |
| camera | null | true/false/toggle | Control local camera (same as video) |
| video | null | true/false/toggle | Control local camera (alternate command) |
| volume | null | true/false/0-100 | Control playback volume of all audio tracks (true=100%, false=0%) |
| record | null | true/false | Start/stop recording the local video stream |
| bitrate | null | true/false/integer | Control video bitrate (true=reset, false=pause, integer=kbps) |
| panning | null | true/false/0-180 | Control stereo panning (90=center) |
| reload | null | null | Reload the current page |
| hangup | null | null | Disconnect current session |
| togglehand | null | null | Toggle raised hand status |
| togglescreenshare | null | null | Toggle screen sharing |
| forceKeyframe | null | null | Force keyframe ("rainbow puke fix") |
| sendChat | null | string | Send chat message to all connected users |
| getDetails | null | null | Get detailed state information |
| soloVideo | null | true/false/toggle | Highlight video for all guests (director only) |
Layout Controls
| Action | Value | Details |
|---|---|---|
| layout | integer/object/array | Switch to layout by index (0=auto-mix, 1-8=custom layouts) or set custom layout with object |
Group Controls
| Action | Value | Details |
|---|---|---|
| group | 1-8 | Toggle in/out of specified group |
| joinGroup | 1-8 | Join specified group |
| leaveGroup | 1-8 | Leave specified group |
| viewGroup | 1-8 | Toggle preview of specified group |
| joinViewGroup | 1-8 | Preview specified group |
| leaveViewGroup | 1-8 | Stop previewing specified group |
Camera Control (PTZ)
| Action | Value | Value2 | Details |
|---|---|---|---|
| zoom | decimal value | abs/true (optional) | Adjust zoom (positive=in, negative=out, or absolute with Value2) |
| focus | decimal value | n/a | Adjust focus (positive=far, negative=near) |
| pan | decimal value | n/a | Adjust pan (positive=right, negative=left) |
| tilt | decimal value | n/a | Adjust tilt (positive=up, negative=down) |
| exposure | 0-1 | n/a | Set exposure level (0=dark, 1=bright) |
Guest Controls (Director Mode)
| Action | Target | Value | Details |
|---|---|---|---|
| forward | guest slot/ID | room name | Transfer guest to specified room |
| addScene | guest slot/ID | 0-8 or scene name | Toggle guest in/out of specified scene |
| muteScene | guest slot/ID | scene (optional) | Toggle guest's mic in scenes |
| mic | guest slot/ID | true/false/toggle | Control guest's microphone |
| camera | guest slot/ID | true/false/toggle | Control guest's camera |
| video | guest slot/ID | true/false/toggle | Control guest's camera (alternate command) |
| hangup | guest slot/ID | null | Disconnect guest |
| soloChat | guest slot/ID | null | Toggle one-way solo chat with guest |
| soloChatBidirectional | guest slot/ID | null | Toggle two-way solo chat with guest |
| speaker | guest slot/ID | null | Toggle guest's speaker |
| display | guest slot/ID | null | Toggle guest's display (ability to see video) |
| group | guest slot/ID | 1-8 | Toggle guest in/out of specified group |
| forceKeyframe | guest slot/ID | null | Trigger keyframe for guest (fix rainbow artifacts) |
| soloVideo | guest slot/ID | null | Toggle highlighting guest's video |
| sendChat | guest slot/ID | message text | Send private chat message to guest |
| sendDirectorChat | guest slot/ID | message text | Send message and overlay it on guest's screen |
| volume | guest slot/ID | 0-200 | Set guest's microphone volume |
| startRoomTimer | guest slot/ID | seconds | Start countdown timer for guest |
| pauseRoomTimer | guest slot/ID | null | Pause timer for guest |
| stopRoomTimer | guest slot/ID | null | Stop timer for guest |
| mixorder | guest slot/ID | -1 or 1 | Adjust guest's position in the mixer (-1=up, 1=down) |
Response Formats
Responses from the API depend on the method used:
- HTTP GET: Responses include simple text values like
true,false,null,fail, or a specific value. For object responses likegetDetails, you'll receive JSON. - WebSocket: Responses are JSON objects containing the result along with the original request parameters and any custom data fields you included.
- HTTP POST: Responses are JSON objects with similar structure to WebSocket responses.
- SSE: Events are JSON objects containing state updates and other notifications.
Custom Layout Format
When using the layout command, you can provide a custom layout object or array:
// Single layout example
{
"action": "layout",
"value": [
{"x": 0, "y": 0, "w": 50, "h": 100, "slot": 0},
{"x": 50, "y": 0, "w": 50, "h": 100, "slot": 1}
]
}
// Using URL parameter to define multiple layouts
// ?layouts=[[{"x":0,"y":0,"w":100,"h":100,"slot":0}],[{"x":0,"y":0,"w":50,"h":100,"slot":0},{"x":50,"y":0,"w":50,"h":100,"slot":1}]]
// Layout properties:
// x, y: Position (percentage of container)
// w, h: Width and height (percentage of container)
// slot: Guest slot number (0-indexed)
// c: Crop to fit (true/false)
// z: Z-index (layer)
// To switch layouts:
// {"action": "layout", "value": 0} // Auto-mix
// {"action": "layout", "value": 1} // First custom layout
// {"action": "layout", "value": 2} // Second custom layout