Skip to content

Web Control API

The control surface is an HTTP web API, a raw TCP control socket for Stream Deck, and a vector:// link scheme.

Vector listens on two ports. One serves the Web Control API once enabled, and the other the Stream Deck plugin over a raw TCP socket.

The API answers data — JSON, plain text, or an MJPEG stream.

Except for that route index, the routes are shared with Laika, so a script for one works on the other.

The switch is the preference web_api_enabled, off by default. Three things turn it on. See Preferences for that window.

  1. The Enable Web API checkbox. Menu → File → Preferences → API tab, section Web Control API. The help under it shows REST API for remote control. Requires restart to apply changes.
  2. PATCH /api/preferences. Setting web_api_enabled, web_api_port or web_api_bind here rebinds the listener.
  3. Installing the Stream Deck plugin. With vector.sdPlugin and a readable manifest.json in the plugins folder, Vector sets the preference, saves it and rebinds.

VECTOR_STREAMDECK is a separate switch for the control socket.

SurfacePortWhere the number comes from
Web Control API1893the Port: field in Preferences
Stream Deck control socket1894the port the plugin dials

The ports must differ. Change the web port under Menu → File → Preferences → API → Port:.

Menu → File → Preferences → API → Answer on: offers All network interfaces and This machine only.

All network interfaces binds 0.0.0.0:{port}, and anything that can reach the machine can drive Vector.

This machine only binds 127.0.0.1:{port}, and only a program on this machine can reach it.

The listener prints Web API listening on http://{addr}, and a change takes effect the same frame.

Add a firewall rule on a network you do not trust, and leave the API off when you do not need it.

Vector’s command line accepts four words, and --help lists them. Anything else is answered once and then ignored:

vector: ignoring unknown argument `--web-api-port` — try --help

Every JSON answer has these headers.

HeaderValue
Content-Typeapplication/json
Cache-Controlno-store, no-cache, must-revalidate
Access-Control-Allow-Origin*
Access-Control-Allow-MethodsGET, POST, PATCH, DELETE, OPTIONS
Access-Control-Allow-HeadersContent-Type

The MJPEG stream sends its own headers, including Access-Control-Allow-Origin: *. OPTIONS is answered for any path with 200, an empty body and the same headers.

  • /api/text* has its own preflight. There, OPTIONS is 204, the body is {}, and the headers are Access-Control-Allow-Origin: * and Access-Control-Allow-Methods: GET, POST, OPTIONS.
  • Use a tool, not a browser page, for the index. GET /api has no Access-Control-Allow-Origin, and a page on another origin can reach every other route.

GET /api is Vector’s own route. It lists every route the API answers, shared and Vector’s own.

{
"product": "vector",
"routes": [
{"method":"GET","origin":"shared","path":"/api/status","summary":"The application's status document."},
{"method":"GET","origin":"vector","path":"/api","summary":"This document: every route the API answers, shared and Vector's own."}
]
}

origin is "shared" or "vector", and shared routes come first. A route is listed only when the API answers it, so every path is callable.

The index has 42 entries: the 41 shared routes, and GET /api itself.

Every route below is answered and listed by Vector, and the right-hand column is that route’s own summary text.

MethodPathWhat it does
GET/api/statusThe application’s status document.
GET/api/snapshotA headless snapshot of what is on screen.
GET/api/sourcesThe sources discovery can see.
GET/api/viewersEvery viewer’s assignment.
POST/api/viewers/{index}Assign a source to a viewer.
DELETE/api/viewers/{index}Clear a viewer.
POST/api/viewers/{index}/captionSet or clear a viewer’s caption.
POST/api/viewers/{index}/bandwidthSet a viewer’s bandwidth.
PATCH/api/viewers/{index}/stereo_pairsSet a viewer’s stereo pairs.
GET/api/audio/input-devicesThe audio input devices this machine has.
POST/api/audio/outputSend a viewer’s audio to the output.
DELETE/api/audio/outputClear the output audio.
POST/api/layoutSwitch to a layout by name.
GET/api/layoutsThe layouts this install has.
GET/api/layouts/designerThe designer layouts.
POST/api/layouts/designerCreate a designer layout.
PATCH/api/layouts/designer/renameRename a designer layout.
PATCH/api/layouts/designer/{name}Update a designer layout.
DELETE/api/layouts/designer/{name}Delete a designer layout.
GET/api/outputThe output’s status.
POST/api/outputEnable or disable the output.
POST/api/project/{index}Project a viewer fullscreen.
DELETE/api/projectExit projection.
GET/api/licenseThe license’s status.
POST/api/license/activateActivate a license key.
POST/api/license/deactivateDeactivate the license.
GET/api/preferencesThe application’s preferences.
PATCH/api/preferencesChange the preferences.
POST/api/stillSave a still of a source or the window.
POST/api/shutdownShut the application down gracefully.
GET/api/streamThe MJPEG preview stream.
GET/api/textThe default text API value.
GET/api/text/{id}A text API value.
POST/api/text/{id}Set a text API value.
GET/api/text_apisThe text API endpoints.
PUT/api/text_apisReplace the text API endpoints.
PATCH/api/text_apisChange the text API endpoints.
POST/api/text_apis/{id}/sendSend a value to a text API endpoint.
POST/api/iso/startStart ISO recording a viewer.
POST/api/iso/stopStop ISO recording.
GET/api/iso/statusThe ISO recording state.
  • GET /api/viewers shows 5 slots.
  • POST /api/shutdown answers {"message":"Headless shutdown requested","ok":true}, and nothing else. Quit from the File menu, or close the window.
  • The three /api/iso/* routes. POST /api/iso/start and POST /api/iso/stop answer 501 {"error":"ISO recording not enabled in this build","ok":false}, and GET /api/iso/status answers 200 {"recording":[]}.
  • GET /api/status has webrtc as false in Vector, alongside licensed, version and the output flags.
  • POST /api/output needs enabled, which sets the direction. A request that omits enabled, spells it differently, or gives a value that is not true or false is refused with 400 {"error":"Missing 'enabled' field","ok":false}. true switches it on, false switches it off.

Nine routes the API answers stay out of the index: /api/launchpads, /api/launchpad, /api/launchpad/ndi, /api/launchpad/decklink, /api/launchpad/enabled, /api/launchpad/audio and /api/discovery. Names are filtered by path, so both methods of /api/launchpad and of /api/discovery go with them.

A client that calls one of these gets the same answer Laika gives. A Vector session does have one pad, with the layout and its sources, so /api/launchpads shows it. /api/discovery uses your NDI® configuration file.

GET /api/stream is the HTTP listener’s one server-side push. It answers HTTP/1.1 200 OK with its own headers: the media type multipart/x-mixed-replace with boundary=frame, plus Access-Control-Allow-Origin: *, Cache-Control: no-cache, no-store and Connection: keep-alive. A part goes out every ~100 ms, each Content-Type: image/jpeg with its Content-Length.

The connection stays open until the client disconnects.

With the API enabled and a page live, Vector scales the composed picture to 960x540 and encodes a JPEG. The stream is the composed page at half resolution, without any scope tile’s picture.

This is where the Stream Deck plugin talks, over a raw TCP socket, not HTTP. Vector exchanges one JSON object per line.

The version gate is the protocol number, 2. Any other number in a Hello is refused, with a sentence giving the number Vector uses. A line may not exceed 64 KiB, and a longer one is answered message longer than 65536 bytes.

Three replies:

{"kind":"ok","id":1,"feedback":{"active":false}}
{"kind":"error","id":1,"message":"no action is named `Quit`"}
{"kind":"options","id":1,"options":[{"value":"studio","label":"Studio A"}],"selected":"studio"}

And one push:

{"kind":"state","context":"<tile>","feedback":{"active":false}}

Every feedback object has active, plus title, readout and indicator when they exist. A push goes to every connected surface. Vector checks each subscribed tile continually and pushes only on a change. With no client it sends nothing and stores nothing.

A Poll subscribes its tile, a Release unsubscribes, and a surface that disconnects has all its tiles forgotten.

The plugin is a compiled program that Stream Deck runs directly. It calls itself Fetch Media Tools | Vector, and it needs Stream Deck 6.0 or later, on macOS 10.15 or Windows 10.

Install it from Menu → File → Install Stream Deck Plugin. The entry is offered even where Stream Deck is not installed, and answers “Stream Deck is not installed on this machine”.

A click installs at the click and opens the Stream Deck Plugin window, one label and one Close button, showing Nothing has been installed yet. before anything is asked.

PlatformInstall location
macOS$HOME/Library/Application Support/com.elgato.StreamDeck/Plugins/vector.sdPlugin
Windows%APPDATA%\Elgato\StreamDeck\Plugins\vector.sdPlugin

Any click on the entry installs. It replaces whatever is there, older or newer. The refusals you can meet are the protocol number above and a stale saved port, answered the saved control port {n} is not the port this socket uses, so 1894 is being dialled instead.

The plugin offers 19 actions plus the connection tile Vector Connection, from a catalogue of 93 in six categories. One more, Load layout at index, is retired but still served. Eight of them work on a dial, and the dial turns four properties.

Dial propertyRangeDefaultStep per detent
Global brightness0.0-4.01.50.05
Scope Zoom Reset0.25-8.01.00.25
Waveform zoom0.5-20.01.00.5
Snapshot blend0-100505

A readout is the value, as 50 % or 1.00x, with an indicator from 0 to 100. A turn sets the value on the scope it is aimed at, the same value the scope properties pane edits and the layout saves. Global brightness is per-scope, and a dial is how you reach it. It scales the finished trace, while Gain magnifies what the scope measured.

The grammar is vector://action/<identifier>?index=<n>&text=<value>, with index first. You can leave the action/ segment out. Unknown keys and repeated keys are malformed, and every byte outside A-Za-z0-9-_.~ is percent-encoded.

LinkMeaning
vector://action/Quitthe Quit action
vector://Quitthe short form of the same thing
vector://action/LoadLayoutIndex?index=2load the layout at index 2
vector://action/LoadLayout?text=Studio%20Aload the layout called Studio A
vector://action/ConnectDeviceAtSlot?index=3&text=DeckLink%201connect a named device at slot 3

Refused forms include "", "Quit", an omniscope:// link, vector://, vector://action/, vector://other/Quit, vector://action/Quit?index=0, vector://action/Quit?index=abc, an unknown key such as ?slot=2, a repeated index or text, and a truncated percent escape.

Put a vector:// link on a Stream Deck key. A mapping has the link, and Stream Deck hands it to Vector. No operating system registers the scheme, so a link pasted into a browser does nothing.

The web API answers with one JSON object and a status from one table.

{"error":"Not found","ok":false}
StatusMeaningExample body
400bad requestMissing 'source' field
402payment or licenselicense required, in the table, though nothing you can reach returns it
404no such routeNot found
405wrong methodMethod not allowed
409conflictthe route’s own sentence
500internal errorthe route’s own sentence
501not implemented{"error":"ISO recording not enabled in this build","ok":false}
503shutting downApp shutting down
504timeoutTimeout

Some sentences come from the API itself, before any route acts. All are 400 except Method not allowed, which is 405. They include Missing 'name' field, Missing 'source' field, Missing 'viewer' field, Missing 'license_key' field, Missing 'old_name' or 'new_name', Missing 'name' or 'enabled', Invalid viewer index, Method not allowed, 'pairs' must be an array of 8 booleans, Missing or invalid 'mode' (use global/full/proxy), Missing text API endpoint id and Missing layout name.

The control socket uses the same idea in its own shape: {"kind":"error","id":<n>,"message":"<sentence>"}. Each sentence wraps the interpolated name in backticks.

no action is named `{id}`
`{action}` needs {a} {expected}
`{action}` does not take a {supplied}
`{action}` was given an empty {expected}
`{action}` takes an index in {min}..={max}, not {value}
`{action}` has no dial behaviour, so it cannot be put on a dial
`{uri}` is not a Vector action link
this build cannot perform `{action}`
this build cannot perform `{action}`: {reason}
`{action}` could not be performed: {reason}

The dispatcher also answers no button is bound to channel {channel} and no dial is bound to channel {channel}, and the connection answers the two protocol sentences.

Terminal window
curl http://localhost:1893/api
curl http://localhost:1893/api/status
curl -X POST http://localhost:1893/api/viewers/0 \
-H "Content-Type: application/json" \
-d '{"source":"CAMERA-1"}'
curl http://localhost:1893/api/license

The payloads and their fields mean the same in both products. Working with Scopes covers the scope tiles you are driving, and QC and the Error Log covers what Vector records about them.