Visibox API
Visibox exposes a local network API for remote control, automation, and custom integrations. This page is the technical reference. If you want to use Visibox through an AI assistant or a shell, see AI Assistants or Command Line Interface instead. If you want to drive it from a control surface, see OSC.
Overview
The API runs in a separate process for stability. If your integration misbehaves, it does not affect Visibox itself. Three services are available:
| Service | Port | Protocol | Description |
|---|---|---|---|
| WebSocket | 17734 | ws:// | Real-time two-way communication. State updates are pushed to connected clients. |
| HTTP REST | 17736 | http:// | Request and response, for querying state and sending commands. |
| OSC | 17735 | UDP | Open Sound Control. See OSC. The port can be changed in Settings > Remote. |
All three bind to every network interface on the machine, so any device on the same local network can connect.
These port numbers changed in Visibox 6.0: WebSocket moved from 51734 to 17734, OSC from 51735 to 17735, and HTTP from 51736 to 17736. If you have an integration built against the older numbers, update them.
Service Discovery
Visibox advertises itself on the local network using Bonjour (mDNS/DNS-SD):
| Service Type | Advertised as | Port |
|---|---|---|
_visibox._tcp | Visibox - <hostname> | 17734 |
_http._tcp | Visibox HTTP - <hostname> | 17736 |
The TXT records include the API version, hostname, platform, and the port numbers, including oscPort. OSC has no Bonjour service of its own.
Authentication
Localhost
Connections from 127.0.0.1 or ::1 never need a pairing code. An HTTP request from the same machine needs no token either, as long as it addresses Visibox as localhost, 127.0.0.1, or [::1]. One that uses the computer’s network name or IP address instead needs a token, like a remote device. That is how the CLI and the MCP server connect with no setup.
A WebSocket client on the same machine, such as the Stream Deck plugin, Bitfocus Companion, or Node-RED, asks once. Visibox shows an approval prompt the first time it connects, and remembers the answer. See Connecting below.
Remote devices
Devices connecting over the network must pair first. See Remote Pairing for how to pair a device and how long the token lasts.
Once paired, the device includes its token in every request, either as an Authorization: Bearer <token> HTTP header or in the initial WebSocket auth message.
HTTP REST API
Base URL: http://<visibox-ip>:17736
Health and info
GET /health
GET /api/v1/info
/health needs no authentication and is the quickest way to check the API is up. /api/v1/info returns the API version, the app version, and a map of every endpoint.
Pairing
POST /api/v1/pair
POST /api/v1/refresh
POST /api/v1/pair exchanges a 6-digit code for a token:
{
"otp": "123456"
}
Returns:
{
"token": "eyJhbGciOi..."
}
Projects
GET /api/v1/projects
GET /api/v1/projects/recent
GET /api/v1/projects/:projectId
GET /api/v1/projects/:projectId/summary
GET /api/v1/projects/:projectId/playstate
GET /api/v1/active
GET /api/v1/app-state
/projects lists every open project. /projects/recent lists the Projects in File > Open Recent, most recent first, by name. Localhost clients also get each Project’s path, which the project:open edit accepts. /projects/:projectId returns everything: Songs, Clips, files, Effects, Visualizers, and display options. /summary is the light version of that, for a client that only needs names and counts.
/playstate returns what is playing, with positions, durations, and directions. /active bundles the active project and its play state into one request. /app-state returns full screen, mute, volume, and recording state, plus the Output’s master hue and intensity, the audio settings, the audio output and input devices, the MIDI inputs and maps, the render caps and Polyopsis, the Output’s flip, rotation, and aspect, the Output targets, the OSC port, camera preconnect, and which optional features are turned on, under features (see App settings edits).
Drill-down endpoints
For part of a Project without fetching the whole thing:
GET /api/v1/projects/:projectId/songs
GET /api/v1/projects/:projectId/songs/:songId
GET /api/v1/projects/:projectId/clips?ids=<id>,<id>
GET /api/v1/projects/:projectId/effects
GET /api/v1/projects/:projectId/visualizers
GET /api/v1/projects/:projectId/targets
/clips takes a required ids query parameter and returns only those Clips. /targets lists every Real-Time Control Target the Project accepts, with its ID, label, group, type, and range. These are the IDs SET_PARAM takes. Add ?clipID=<id> to list one Clip’s Targets only.
Presets
GET /api/v1/presets
GET /api/v1/presets/:kind/:id
GET /api/v1/presets/:kind/:id/controls
POST /api/v1/presets/validate
List the Effect and Visualizer presets, or fetch one. :kind is effect or visualizer. /controls returns the adjustable parameters a preset exposes, and an empty list for a preset that exposes none.
POST /api/v1/presets/validate checks a preset without saving or rendering it. The body names the kind and carries the preset:
{
"kind": "effect",
"preset": {}
}
It returns valid, plus errors and warnings lists whose entries each carry a message and, when it applies, a field.
Media
GET /thumbnails/:projectId/:fileId.jpg
A file’s thumbnail. Pass a Clip ID as fileId instead to get that Clip’s thumbnail with its filters and Effects drawn in. Optional query parameters:
| Parameter | Default | Description |
|---|---|---|
size | original | Square output size in pixels (16 to 512). |
overlay | none | Overlay icon: play, pause, or none. |
format | jpeg | Output format: jpeg or png. |
quality | 85 | JPEG quality, 1 to 100. |
GET /waveforms/:projectId/:fileId.png
A waveform image for an audio file.
Cameras
GET /api/v1/cameras
GET /camera-thumbnails/:cameraId.jpg
List cameras with their device IDs and labels, or fetch a live thumbnail, updated every 5 seconds while the camera is active. Use default as the camera ID for the default camera.
Audio devices
GET /api/v1/audio/devices
Lists the audio output and input devices:
{
"outputs": [{ "id": "abc", "label": "MacBook Speakers", "isCurrent": true, "settable": true }],
"inputs": [{ "id": "def", "label": "Built-in Mic", "isCurrent": false, "settable": true }]
}
id is the value the audio:set-output-device and audio:set-default-input edits take. isCurrent marks the stored selection. settable is false for ASIO outputs, which the API cannot select.
Sending actions
POST /api/v1/action
The body names the action and its parameters:
{
"name": "PLAY_CLIP_BY_INDEX_IN",
"params": ["0", "1"],
"base": 0
}
| Field | Required | Description |
|---|---|---|
name | Yes | An action name from the list below. |
params | No | Array of values. Everything is coerced to a string. |
projectID | No | Target project. Omit for the active one. |
base | No | 0 or 1. See Counting from zero or one. |
Editing a Project
POST /api/v1/edit
GET /api/v1/edit/schema
GET /api/v1/edit/schema/:name
POST /api/v1/edit applies a Project mutation: add, move, or delete Songs, Clips, Effects, and Visualizers, set a Song’s timeline or lyrics, import and manage media, build Setlists, place Overlays, set up Real-Time Control, copy Songs and Clips from another open Project, undo, redo. It also manages the overlay library with overlay-preset:create, overlay-preset:update, and overlay-preset:delete. overlay-preset:create takes an image path on the same machine. It also creates, opens, saves, closes, and switches Projects. An edit that takes a file path accepts it only from the same machine, and only as an absolute path. The two schema endpoints list the available edit names and the parameters each one takes, which is the reliable way to discover them rather than hard-coding a list.
An edit that would leave a Clip’s In Point at or after its Out Point, or past the end of the file, is refused with VALIDATION_FAILED. Clip speed is held between 0.0625 and 16. A Clip takes at most 8 Effect layers.
song:set-program-id and song:bulk-set-program-id take a Program ID from 1 to 128 and an optional bank from 1 to 128, the numbers the app shows. Leave bank out to keep each Song’s current bank. Reads report the stored values, one lower, as programID and programBank.
App settings edits
A few edits change app-wide settings rather than a Project, so they need no Project open. Each takes effect at once and shows in Settings.
| Edit | Parameters | Description |
|---|---|---|
audio:set-output-device | deviceId | Switch the audio output device. ASIO devices are refused. |
audio:set-imaging | imaging | Set the output channel mode: normal, mono, or multitrack. |
audio:set-default-input | deviceId | Set the default audio input device. An empty string clears it. |
audio:set-remember-mute-state | enabled | Restore the last mute state at launch. |
midi:set-input-enabled | inputID, enabled | Open or close a MIDI input. |
midi:add-mapping | deviceId (optional), trigger, action | Bind a MIDI message to an action. Replaces any binding the trigger already has. |
midi:remove-mapping | deviceId (optional), trigger | Remove the action bound to a MIDI message. |
render:set-caps | resolutionCap, frameRateCap, sharpRender, letterbox | Cap the render. resolutionCap is 720, 1080, 4K, 8K, or null for no cap. frameRateCap is 24, 30, 60, 120, or null. Send only what changes. |
polyphony:set | max | Set Polyopsis, how many Clips can play at once. The API takes 1 to 16. |
output:set-transform | flip, rotation, stageRatio | Flip the Output (none, horizontal, vertical, both), rotate it (0, 90, 180, 270), or set its stage ratio (none or W:H, such as 16:9). Send only what changes. |
output:set-aspect | ratio | The Output window’s shape: none, W:H, or width divided by height as a number. |
output-target:set-enabled | targetId, enabled | Turn a display or NDI target on or off as an Output target. Turning on a display takes it away from the Controller or Lyrics. |
osc:set-port | port | The OSC port, 1024 to 65535. Takes effect after Visibox relaunches. |
camera:set-preconnect | webcam, ndi | Connect to that kind of camera before its Clips play. Send only what changes. |
Device IDs come from GET /api/v1/audio/devices. The current values, the MIDI input IDs, and the MIDI maps come from GET /api/v1/app-state, as audioOutput, audioImaging, defaultAudioInput, rememberMuteState, midiInputs, midiMaps, renderResolutionCap (left out when there is no cap), renderFrameRateCap, sharpRender, outputViewLetterbox, maxPolyphony, flip, rotation, stageRatio, outputAspectRatio, outputTargets, apiOscPort, webcamPreconnect, and ndiCameraPreconnect. A W:H ratio must fall between 1:10 and 10:1. A targetId must be an id from outputTargets.
For the MIDI edits, trigger is cc:<0-127>, noteon:<0-127>, or programchange. deviceId is a MIDI input ID, or default (the map every input without its own map uses), which is also what you get when you leave it out. action is an action name with its arguments after colons, such as PLAY_IN or OUTPUT_LEVEL_IN:%. To drive a Control from a fader, bind a CC to SET_CONTROL:A:%, where A is the Control’s letter. programchange can only be bound to SONG_SELECT_IN or SONG_SELECT_PGM_ID.
Uploading a file
POST /api/v1/files/upload
WebSocket API
Connect to ws://<visibox-ip>:17734/v1. The /v1 path is required.
The WebSocket API is two-way. Connect once, authenticate, and state updates are pushed to you as things change, with no polling.
Connecting
As soon as the socket opens, and before you have sent anything, the server pushes a connected event carrying your client ID. It is a greeting, not an acknowledgement: every WebSocket connection authenticates, localhost included.
Send an auth message next:
{
"type": "auth",
"payload": {
"token": "your-token"
}
}
On success the server replies with a response message carrying the same id as your auth message and data: { "authenticated": true }. Wait for that before you send anything else. A remote device pairing for the first time sends otp and deviceName instead of token. A client on the same machine can send auth with neither: Visibox puts an approval prompt on screen, and the response that follows carries a token to reuse next time. See Remote Pairing.
Receiving state updates
The server pushes state-update events:
{
"type": "event",
"event": "state-update",
"payload": [
{
"updateType": "playstate",
"projectId": "p1234567890",
"data": {
"event": "clip-play-state-changed",
"clipID": "c001",
"newState": "playing"
}
}
]
}
event sits beside payload, not inside it, and payload is the array of updates itself. There is no wrapper key around it.
Update types are project, playstate, clip, song, volume, appState, presets, and full. appState carries app-level changes (full screen, mute, output level) and its projectId is null, because it belongs to no one project. presets fires when an Effect or Visualizer preset is added, changed or removed, so a client holding a cached preset list knows to refresh it. Updates are coalesced, so a burst of changes arrives as one message rather than fifty.
Sending actions
{
"id": "req-123",
"type": "action",
"action": {
"name": "NEXT_SONG_IN",
"params": []
},
"base": 1
}
The server responds with a response message carrying the same id, so you can match responses to requests. base sits beside action, not inside it.
Available Actions
Action names are uppercase and go in the name field over any transport.
Playback
| Action | Parameters | Description |
|---|---|---|
PLAY_IN | none | Start playback. |
STOP_IN | none | Stop playback. |
PAUSE_IN | clipID (optional) | Pause. |
RESUME_IN | clipID (optional) | Resume from a pause. |
PLAY_TOGGLE_IN | none | Toggle play and stop. |
PAUSE_TOGGLE_IN | none | Toggle pause and resume. |
PLAY_CLIP_IN | clipID (optional) | Play a Clip by ID. With no clipID and nothing playing, it is refused. |
PLAY_CLIP_BY_INDEX_IN | index, songID (optional) | Play a Clip by its position. |
TRIGGER_CLIP_IN | clipID, activateSong (optional) | Trigger a Clip, respecting its retrigger behavior. |
RESTART_CLIP_IN | clipID (optional) | Restart a Clip from the beginning. |
RELEASE_CLIP_IN | clipID | Release a held Clip. See below. |
RELEASE_CLIP_BY_INDEX_IN | index, songID (optional) | Release a held Clip by its position. |
STOP_CLIP_IN | clipID, maybePlayBackground (optional) | Stop one Clip. |
STOP_ALL_CLIPS_IN | maybePlayBackground (optional) | Stop every Clip. |
RELEASE_CLIP_IN and RELEASE_CLIP_BY_INDEX_IN are new in 6.0. They are the note-off half of a held button, and they do nothing unless the Clip is in Gate launch mode. That makes them safe to send unconditionally: build your surface to send trigger on press and release on release, and Clips in the other launch modes ignore the release.
Navigation
| Action | Parameters | Description |
|---|---|---|
NEXT_CLIP_IN | anySong (optional) | Next Clip. Pass true to cross into the next Song. |
PREV_CLIP_IN | anySong (optional) | Previous Clip. |
RANDOM_CLIP_IN | scope (optional) | A random Clip: song (the default) picks from the active Song, all from every Song. |
NEXT_SONG_IN | noStop (optional) | Next Song. Pass true to leave playback running. |
PREV_SONG_IN | noStop (optional) | Previous Song. |
PLAY_NEXT_SONG_IN | none | Move to the next Song and start playing it. |
SONG_SELECT_IN | index, stopPlaying (optional) | Select a Song by its position. |
SONG_SELECT_BY_ID | songID, stopPlaying (optional) | Select a Song by ID. |
SONG_SELECT_PGM_ID | pgmId, stopPlaying (optional) | Select a Song by its Program ID and bank. |
SONG_SELECT_PERCENT_IN | percent, stopPlaying (optional) | Select a Song by position through the list, 0.0 to 1.0. |
stopPlaying defaults to true.
pgmId counts from zero and packs the bank in with it: bank × 128 + program, up to 16383. Program 5 in bank 0 is 4. Program 5 in bank 2 is 260. A Project that assigns no banks matches on the program alone.
Seeking and direction
| Action | Parameters | Description |
|---|---|---|
SEEK_CLIP_IN | clipID, percent | Seek a Clip. percent is 0.0 (start) to 1.0 (end). |
SEEK_SONG_IN | songID, percent | Seek Song audio, 0.0 to 1.0. |
CLIP_DIRECTION | clipID, direction | Set direction: forward or backward. |
Both seek actions take a fraction from 0.0 to 1.0, not a number of seconds. Older documentation described them as seconds. The CLI seek command takes 0 to 100 and converts, so the two differ on purpose.
Real-Time Control
| Action | Parameters | Description |
|---|---|---|
SET_CONTROL | controlID, value | Drive a Control. |
SET_PARAM | targetID, value | Write one Target directly, skipping Controls. |
Both are new in 6.0. controlID is a Control’s letter, A through Z, case-insensitive. targetID is of the form clip:abc123/opacity. For SET_CONTROL, values are conventionally 0.0 to 1.0 (or -1.0 to 1.0 for a bipolar Control) and are not clamped here: shaping happens in the Control’s own settings.
SET_PARAM is different. Its value is a number in the Target’s own units, using the range and unit that /targets reports, so it is not 0.0 to 1.0. Send a percentage string such as "50%" to map onto the Target’s range instead. See Real-Time Control.
A Control in a read of the active Project carries its current value when Visibox can supply one. It is left out when the Output window is not running or the Control has not been moved since launch.
System
| Action | Parameters | Description |
|---|---|---|
OUTPUT_LEVEL_IN | level (0.0 to 1.0) | Set the master output level. |
MUTE_TOGGLE | state (optional) | Toggle mute. Pass on to mute or off to unmute. |
FULLSCREEN_TOGGLE | state (optional) | Toggle full screen. Pass on or off to set it instead. |
RECORD_TOGGLE | mode (optional) | Toggle recording the output to a file. |
PANIC | none | Reload every window. |
OPEN_EDITOR | kind, presetID | Open the Effects or Visualizers editor on a preset. kind is effect or visualizer. |
RECORD_TOGGLE is new in 6.0. When nothing is recording, mode chooses how it starts: now begins immediately, next-trigger arms it to begin on the next Clip trigger. When recording is already armed or running, the action disarms or stops it and mode is ignored.
Velocity
Actions that start a Clip carry an optional velocity field alongside params, from 0 to 127, matching a MIDI note-on. It applies to TRIGGER_CLIP_IN, PLAY_CLIP_IN, PLAY_CLIP_BY_INDEX_IN, and RESTART_CLIP_IN, and feeds the Clip’s Velocity Sensitivity setting. Omit it and the trigger counts as full velocity.
Counting from zero or one
Clip and Song positions are zero-based by default: the first Clip is 0. The Controller numbers them from one.
Send base: 1 alongside an action and Visibox subtracts one from the first parameter of PLAY_CLIP_BY_INDEX_IN, RELEASE_CLIP_BY_INDEX_IN, and SONG_SELECT_IN, so your integration can use the numbers your user sees. Those three are the only actions it touches, and it does not apply to edit parameters, which are always zero-based.
Send the release with the same base you sent the trigger with. A gate button whose press and release disagree on the base holds one Clip down and lets go of its neighbor.
MCP Tools Reference
The AI Assistants integration is powered by an MCP server exposing tools to AI clients. It is served from the same HTTP port at /mcp, with discovery at GET /.well-known/mcp.json. Three methods are registered on that path: POST sends a request, GET opens a server-sent-events stream for messages coming back, and DELETE ends the session. An MCP client library handles all three for you. For end-user setup, see the AI Assistants page. This section covers the tools themselves.
Query tools
| Tool | Description |
|---|---|
visibox_get_projects | List all open Projects. |
visibox_get_recent_projects | Recently opened Projects, most recent first. |
visibox_get_project | Full data for a Project: Songs, Clips, files, Effects, Visualizers. |
visibox_get_project_summary | Names and counts only, for a cheap overview. |
visibox_get_active_project | The active Project and its play state in one call. |
visibox_get_playstate | Play state for a Project. |
visibox_get_app_state | App-level state: full screen, mute, volume, audio, MIDI, render caps, Output transform and targets, OSC port, camera preconnect, and optional features. |
visibox_get_songs | List Songs in a Project. |
visibox_get_song | One Song, including its timeline and lyrics. |
visibox_get_clips | Several Clips by ID. |
visibox_get_clip | One Clip. |
visibox_get_effects | Effects in a Project. |
visibox_get_visualizers | Visualizers in a Project. |
visibox_get_presets | List Effect and Visualizer presets. |
visibox_get_preset | One preset by ID and kind. |
visibox_get_preset_controls | The parameters a preset exposes. |
visibox_get_cameras | List cameras with their device IDs and labels. |
visibox_get_camera_thumbnail | The latest frame from a camera, as an image. Takes a camera ID or default. The camera must be active. |
visibox_get_clip_thumbnail | A Clip’s thumbnail, as an image. |
visibox_get_clip_waveform | A Clip’s audio waveform, as an image. Only Clips with sound have one. |
visibox_get_audio_devices | List audio output and input devices. |
Edit tools
| Tool | Description |
|---|---|
visibox_clip_edit | Add, move, duplicate, rename, delete Clips, point a Clip at a different file, copy Clips from another open Project, or update Clip options including filters, effects, and tweaks. |
visibox_song_edit | Add, move, duplicate, delete Songs, manage Song audio, titles, lyrics, Poly Mode and Overlays, set Program IDs and timelines, and copy Songs from another open Project. |
visibox_project_edit | Create, open, save, close, and switch Projects. Undo, redo, title, display options, Background Clips, zoom, Setlists, Overlays, and Real-Time Control Controls and bindings. |
visibox_effect_edit | Add, update, remove, or copy Effects. |
visibox_visualizer_edit | Add, update, remove, or copy Visualizers. |
visibox_preset_edit | Add, update, or remove user Effects, Visualizers, and overlay library images. |
visibox_file_edit | Import, rename, and delete media files, set or clear a custom thumbnail, and clean up unused files. |
visibox_audio_edit | Change the audio output device, channel mode, default input, and whether mute is remembered. |
visibox_midi_edit | Open or close MIDI inputs, and bind or unbind MIDI messages to actions and Real-Time Controls. |
visibox_render_edit | Change the render caps, Polyopsis, the Output’s transform and window shape, which displays are Output targets, the OSC port, and camera preconnect. |
visibox_describe_edit | Return the parameter schema for one edit name. |
visibox_validate_preset | Check a preset before writing it. |
Action tools
| Tool | Description |
|---|---|
visibox_playback | Playback controls: play, stop, pause, resume, trigger Clips, navigate, seek, volume. |
visibox_system | System controls: full screen and mute (toggle, or set on or off), recording, panic. |
visibox_open_editor | Open the Effects or Visualizers editor on a given preset. |
visibox_control | Real-Time Control: list the Controls or the Targets, set a Control by letter, or set a parameter by Target ID. Listing Targets returns IDs, labels, and groups, up to 200. Pass clipID for one Clip’s Targets, and details: true for types and ranges. |
The MCP tools cover most but not all of the action list. Anything missing can be driven over HTTP, WebSocket, or OSC.
Live editor updates
When visibox_effect_edit or visibox_visualizer_edit modifies a preset, the matching Editor window updates in real time if it is open. Code changes, parameter changes, and the live preview all reflect the edit as it happens.
Stream Deck Plugin
Visibox has an official Elgato Stream Deck plugin that connects through this API. See the Stream Deck page for full details. The Companion connection and the Node-RED nodes use the same WebSocket API.
Security
The API is designed for local network use:
- All communication is unencrypted. Do not expose these ports to the public internet.
- Same-machine HTTP requests need no token, and a same-machine WebSocket client needs one approval, by design, for integrations on the same machine.
- Failed pairing attempts are limited to 5 per IP address every 5 minutes, across HTTP and WebSocket. Past that, Visibox answers
Too many pairing attempts. Try again later., with HTTP status 429. - One-time pairing codes expire after 5 minutes.
Error Handling
HTTP status codes
| Code | Meaning |
|---|---|
| 400 | Bad request. Invalid parameters. |
| 401 | Authentication required, or an invalid token. |
| 403 | Your plan does not include API access. |
| 404 | Resource not found. |
| 500 | Server error, or the action was refused. |
| 503 | Service unavailable. The API is not ready yet. |
An action Visibox refuses comes back as a 500 whatever the reason, so read the error string rather than branching on the status code.
WebSocket error types
| Type | Description |
|---|---|
INITIALIZATION_FAILED | The server could not start. |
CONNECTION_FAILED | The connection could not be established. |
AUTHENTICATION_FAILED | Invalid token. |
ACTION_FAILED | The action could not be executed. |
INVALID_MESSAGE | Malformed message. |
RATE_LIMITED | Too many requests. |
SERVER_ERROR | Internal server error. |