# VDO.Ninja Developer API Sandbox

WebSocket API
HTTP API
Server-Sent Events
HTTP POST
Camera Controls
MIDI Integration
Guest Controls
API Reference
WebSocket API Request Builder
Action:
Value:
Value2 (optional):
Target (optional - for director commands):
Callback ID (optional):
Custom JSON (advanced):
Common Controls (WebSocket)
Layout Controls (WebSocket)

### WebSocket API Code Example

JavaScript

    // 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

HTTP GET API Request Builder
Action:
Target (optional - for director commands):
Value:
Generated URL:

    https://api.vdo.ninja/YOUR_API_ID/action/target/value

Common Controls (HTTP)
Group Controls (HTTP)

### HTTP API Code Example

JavaScript

    // 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

Server-Sent Events (SSE) Listener

SSE provides a one-way communication channel to receive events from VDO.Ninja without sending requests.

SSE Connection Status:
Disconnected

### SSE API Code Example

JavaScript

    // 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();

HTTP POST API Request Builder

Although HTTP GET is the primary API method, POST can be useful for complex data or when query parameters need to be hidden.

Action:
Value:
Target (optional - for director commands):
Request Body JSON:

### HTTP POST API Code Example

JavaScript

    // 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!"
    // });

Camera Controls (PTZ)

Remote control compatible camera hardware using pan, tilt, zoom, focus, and exposure commands.

### Zoom

Absolute Zoom:
0.5

### Focus

### Pan & Tilt

### Exposure

Exposure Level:
0.5

### PTZ Controls Code Example

JavaScript

    // 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

MIDI Integration

Control VDO.Ninja using MIDI controllers with various mapping options.

MIDI Device Status:
MIDI access not initialized
Select MIDI Device:
MIDI Monitor (last received message):

    No MIDI message received

### Default MIDI Mappings

VDO.Ninja supports several MIDI mapping schemes, which can be configured using the `midiHotkeys` parameter.

Standard Mapping (midiHotkeys=1)

- 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

Alternate Mapping (midiHotkeys=2)

- 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

CC-Based Controls (midiHotkeys=3)

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

PTZ MIDI Controls

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

JavaScript

    // 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));

Guest Controls (Director Mode)

These controls allow directors to manage remote guests via the API.

Target Guest:
Action:
Value:
API Method:
Guest Mic Volume:
100

### Guest Control Code Example

JavaScript

    // 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

VDO.Ninja API Reference

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 like `getDetails`, 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:

Layout Object Example

    // 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

API Request/Response Logger