Version: 6.0.0

# 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](https://manual.spaceage.tv/6.0.0/integrations/ai/) or [Command Line Interface](https://manual.spaceage.tv/6.0.0/integrations/cli/) instead. If you want to drive it from a control surface, see [OSC](https://manual.spaceage.tv/6.0.0/integrations/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](https://manual.spaceage.tv/6.0.0/integrations/osc/). The port can be changed in [Settings > Remote](https://manual.spaceage.tv/6.0.0/integrations/remote-pairing/#osc-port). |

All three bind to every network interface on the machine, so any device on the same local network can connect.

note

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](#connecting) below.

### Remote devices

Devices connecting over the network must pair first. See [Remote Pairing](https://manual.spaceage.tv/6.0.0/integrations/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 /healthGET /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/pairPOST /api/v1/refresh
```

`POST /api/v1/pair` exchanges a 6-digit code for a token:

```
{  "otp": "123456"}
```

Returns:

```
{  "token": "eyJhbGciOi..."}
```

### Projects

```
GET /api/v1/projectsGET /api/v1/projects/recentGET /api/v1/projects/:projectIdGET /api/v1/projects/:projectId/summaryGET /api/v1/projects/:projectId/playstateGET /api/v1/activeGET /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](#app-settings-edits)).

#### Drill-down endpoints

For part of a Project without fetching the whole thing:

```
GET /api/v1/projects/:projectId/songsGET /api/v1/projects/:projectId/songs/:songIdGET /api/v1/projects/:projectId/clips?ids=<id>,<id>GET /api/v1/projects/:projectId/effectsGET /api/v1/projects/:projectId/visualizersGET /api/v1/projects/:projectId/targets
```

`/clips` takes a required `ids` query parameter and returns only those Clips. `/targets` lists every [Real-Time Control](https://manual.spaceage.tv/6.0.0/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/presetsGET /api/v1/presets/:kind/:idGET /api/v1/presets/:kind/:id/controlsPOST /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/camerasGET /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](#counting-from-zero-or-one). |

### Editing a Project

```
POST /api/v1/editGET  /api/v1/edit/schemaGET  /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](https://manual.spaceage.tv/6.0.0/poly-mode/#polyopsis-how-many-clips-at-once), 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](https://manual.spaceage.tv/6.0.0/integrations/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](https://manual.spaceage.tv/6.0.0/integrations/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](https://manual.spaceage.tv/6.0.0/launch-modes/). 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`. |

warning

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](https://manual.spaceage.tv/6.0.0/integrations/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](https://manual.spaceage.tv/6.0.0/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](https://manual.spaceage.tv/6.0.0/integrations/ai/) 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](https://manual.spaceage.tv/6.0.0/integrations/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](https://manual.spaceage.tv/6.0.0/stream-deck/) page for full details. The [Companion](https://manual.spaceage.tv/6.0.0/integrations/companion/) connection and the [Node-RED](https://manual.spaceage.tv/6.0.0/integrations/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. |

Source: https://manual.spaceage.tv/6.0.0/integrations/api/
