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. |
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
Visibox authenticates connections from 127.0.0.1 or ::1 automatically. No pairing and no token. Integrations running on the same machine, such as the Stream Deck plugin, the CLI, and the MCP server, connect with no setup.
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/: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/: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, and volume.
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
/clips takes a required ids query parameter and returns only those Clips.
Presets
GET /api/v1/presets
GET /api/v1/presets/:kind/:id
List the Effect and Visualizer presets, or fetch one. :kind is effect or visualizer.
Media
GET /thumbnails/:projectId/:fileId.jpg
A Clip’s thumbnail. 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.
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, import media, undo, redo. 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.
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. |
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. |
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 Identifier. |
SONG_SELECT_PERCENT_IN | percent, stopPlaying (optional) | Select a Song by position through the list, 0.0 to 1.0. |
stopPlaying defaults to true.
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. Values are conventionally 0.0 to 1.0 and are not clamped here: shaping happens in the Control’s own settings. See Real-Time Control.
System
| Action | Parameters | Description |
|---|---|---|
OUTPUT_LEVEL_IN | level (0.0 to 1.0) | Set the master output level. |
MUTE_TOGGLE | none | Toggle mute. |
FULLSCREEN_TOGGLE | none | Toggle full screen. |
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_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. |
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. |
Edit tools
| Tool | Description |
|---|---|
visibox_clip_edit | Add, move, duplicate, delete Clips, or update Clip options including filters and effects. |
visibox_song_edit | Add, move, duplicate, delete Songs, manage Song audio and titles, set Program Identifiers, and set a Song’s timeline. |
visibox_project_edit | Undo, redo, display options, Background Clips, zoom. |
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 and Visualizers in the preset store. |
visibox_file_edit | Import media files. |
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, mute, recording, panic. |
visibox_open_editor | Open the Effects or Visualizers editor on a given preset. |
visibox_control | Real-Time Control: list the Controls, set one by letter, or set a parameter by Target ID. |
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.
Security
The API is designed for local network use:
- All communication is unencrypted. Do not expose these ports to the public internet.
- Localhost connections bypass authentication by design, for integrations on the same machine.
- 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. |