Browse & Inputs API Reference
Four REST routes let a control surface walk the FXCanvas content as folders, activate any item, and preview a live input. These routes drive the Stream Deck Plugin. They are control-surface agnostic, so any HTTP client can use them.
For the rest of the API, see the interactive REST API Reference. For the server and authentication, see the REST API guide.
Conventions
| Topic | Detail |
|---|---|
| Base URL | http://<host>:<port>/api/v1 — default http://127.0.0.1:8080/api/v1. |
| License Pro | The REST server runs only with an activated license. Without a license, the server does not start, so a client gets a connection error and not an HTTP status. See What requires a license. |
| Authentication | Optional. When you set an API key, send X-API-Key: <key> or Authorization: Bearer <key> on every request. |
| Name matching | The API matches an identifier on id first. It then matches on name without regard to case, with space, underscore, and hyphen as equivalent characters. It ignores the whitespace at each end. |
| Error body | { "error": true, "code": "<CODE>", "message": "<text>" }, with an extra details object on ITEM_NOT_FOUND and ACTIVATION_TIMEOUT. |
GET /api/v1/browse
Returns the contents of one folder of the browsable hierarchy.
Method: GET — Path: /api/v1/browse
The hierarchy has up to four root folders, in this order:
/sources— source types/presets— source presets/live— live inputs, one folder for each provider type/files— the media folders of your media library
An item node carries a thumbnailUrl and an activate descriptor that you can send without a change. The thumbnailUrl prefix follows the item kind: /api/v1/thumbnails/live/, /media/, or the prefix for a source type or a preset. A folder node carries a browsePath and a childCount. One item node per response has activeState true, at most, and it is the item that GET /api/v1/sources/current reports. The response omits unavailable devices, unavailable media files, and unsupported media files, and it does not count them.
The API omits a root folder that has no children. A show with no capture hardware does not get /live at all. It does not get an empty /live folder either. After you attach a camera, the folder appears at the next rebuild. The paths stay addressable in both cases. A client with a retained path to a folder that is now empty gets an empty listing, not a 404.
In all other cases the API lists a folder exactly as it is, with every item that it holds. A folder of 30 source types is a long scan on a control surface. The API does not subdivide the folder. Pin the few items that you use to keys of their own. The activate descriptor on every item node makes this possible for any client.
Parameters
| Parameter | In | Required | Range | Default |
|---|---|---|---|---|
path | query | No | 1–512 characters, only A-Z, a-z, 0-9, /, _, -. The API ignores a trailing /. | / (the root) |
depth | query | No | Integer 1–3. 1 returns the direct children with no children array. 2 and 3 expand the nested folders recursively. The children of each folder match a direct request for its browsePath. | 1 |
A folder of 500 nodes or fewer answers within 250 ms. A folder of 501 to 5000 nodes answers within 1000 ms. These times are for depth 1 over loopback.
Example success response
GET /api/v1/browse?path=/live → 200 OK
{
"path": "/live",
"parentPath": "/",
"count": 2,
"items": [
{
"kind": "folder",
"id": "folder:4a91c0de77b2",
"name": "NDI",
"activeState": false,
"browsePath": "/live/ndi",
"childCount": 3
},
{
"kind": "liveInput",
"id": "liveInput:webcam_0",
"name": "Logitech C920",
"activeState": true,
"thumbnailUrl": "/api/v1/thumbnails/live/webcam_0",
"activate": {
"method": "POST",
"path": "/api/v1/inputs/live/select",
"body": { "id": "webcam_0" }
}
}
]
}
items is always an array. An empty folder returns [], never null. parentPath is null at the root.
Error codes
| Status | code | Condition |
|---|---|---|
| 400 | INVALID_PATH | path is longer than 512 characters, or it has characters outside the permitted set. |
| 400 | INVALID_DEPTH | depth is not an integer from 1 to 3. |
| 404 | PATH_NOT_FOUND | path is correct in form, but it names no folder. The active source does not change. |
POST /api/v1/inputs/live/select
Makes one live input the current source.
Method: POST — Path: /api/v1/inputs/live/select
GET /api/v1/browse advertises this route as activate on a liveInput node, and you can post it exactly as advertised. Inside FXCanvas, the route activates the input asynchronously. FXCanvas connects the device. It sends the response after the input connects and the first frame arrives, within a budget of 5000 ms. A request for the live input that is already current changes nothing, and it still answers 200.
Parameters
| Parameter | In | Required | Range |
|---|---|---|---|
id | body | One of id or name | String of 1–512 characters, matched against the device id first. |
name | body | One of id or name | String of 1–512 characters, matched by flexible name matching when no id matches. |
{ "id": "webcam_0" }
Example success response
200 OK
{
"success": true,
"current": {
"id": "webcam_0",
"name": "Logitech C920",
"sourceKind": "live",
"type": "live"
}
}
current uses the same schema as GET /api/v1/sources/current.
Error codes
| Status | code | Condition |
|---|---|---|
| 400 | INVALID_REQUEST | The body carries no usable id or name string of 1 to 512 characters. The active source does not change. |
| 404 | ITEM_NOT_FOUND | No available live input matches the identifier. details.available lists at most 50 identifiers of that kind. |
| 504 | ACTIVATION_TIMEOUT | The input did not connect and deliver a frame within 5000 ms. The source that was current before the request still drives the output. |
{
"error": true,
"code": "ITEM_NOT_FOUND",
"message": "No live input matching 'webcam_9'",
"details": {
"requested": "webcam_9",
"kind": "live input",
"available": ["webcam_0", "ndi_studio_a"]
}
}
POST /api/v1/inputs/file/select
Makes one media file from the media library the current source.
Method: POST — Path: /api/v1/inputs/file/select
GET /api/v1/browse advertises this route as activate on a fileInput node, and you can post it exactly as advertised. FXCanvas loads the file asynchronously. It sends the response after the file loads and drives the output, within a budget of 5000 ms. A request for the file input that is already current changes nothing, and it still answers 200.
Parameters
| Parameter | In | Required | Range |
|---|---|---|---|
id | body | One of id or name | String of 1–512 characters, matched against the media file id first. |
name | body | One of id or name | String of 1–512 characters, matched by flexible name matching when no id matches. |
{ "id": "8c1f4b2ad930" }
Example success response
200 OK
{
"success": true,
"current": {
"id": "8c1f4b2ad930",
"name": "city_loop.mp4",
"sourceKind": "file",
"type": "video"
}
}
Error codes
| Status | code | Condition |
|---|---|---|
| 400 | INVALID_REQUEST | The body carries no usable id or name string of 1 to 512 characters. The active source does not change. |
| 404 | ITEM_NOT_FOUND | No available and supported media file matches the identifier. details.available lists at most 50 identifiers of that kind. |
| 504 | ACTIVATION_TIMEOUT | The media file did not load and take over the output within 5000 ms. The source that was current before the request still drives the output. |
GET /api/v1/thumbnails/live/:id
Returns a preview image for one live input.
Method: GET — Path: /api/v1/thumbnails/live/:id
The requested input can be the connected input with a frame from the last 2000 ms. The response is then that frame. The API encodes the frame at the requested size and keeps the aspect ratio. The longest edge is equal to size, and the shorter edge follows the aspect ratio of the input. In all other cases the response is a placeholder image with the input name and its provider type. A thumbnailUrl from GET /api/v1/browse therefore always answers with an image for a known input.
Parameters
| Parameter | In | Required | Range | Default |
|---|---|---|---|---|
id | path | Yes | String of 1–256 characters, matched exactly first and then by flexible name matching. The first device in enumeration order wins a tie. | — |
size | query | No | Integer 32–512 pixels. The API clamps a value outside the span. A value that is not a whole number takes the default. | 192 |
format | query | No | png or jpg. Any other value takes the default. | png |
If-None-Match | header | No | An ETag from an earlier response for the same preview. | — |
Example success response
GET /api/v1/thumbnails/live/webcam_0?size=192&format=png → 200 OK
HTTP/1.1 200 OK
Content-Type: image/png
ETag: "liveInput-192x108-png-3f9a1c0d77b24e15"
Cache-Control: no-cache
<PNG bytes>
The body is the encoded image. Content-Type is image/png or image/jpeg, to match format. The ETag is "<kind>-<width>x<height>-<format>-<digest>". A leading p on the digest marks a placeholder render. A live frame gives a new ETag for each frame. A placeholder ETag is stable while the input stays disconnected or stale.
Conditional requests
Send the request again with If-None-Match set to the ETag that you hold. The response is 304 Not Modified with no body, before the API queues any render work:
HTTP/1.1 304 Not Modified
ETag: "liveInput-192x108-png-3f9a1c0d77b24e15"
Cache-Control: no-cache
Cache-Control is always no-cache. A client therefore revalidates with If-None-Match, and it does not serve a stale image.
Error codes
| Status | code | Condition |
|---|---|---|
| 404 | DEVICE_NOT_FOUND | :id is empty, longer than 256 characters, or matches no enumerated live input. |
| 503 | PREVIEW_TIMEOUT | The queued frame readback did not complete within 500 ms. The frame data of the device does not change. |
| 503 | THUMBNAIL_QUEUE_FULL | The render queue is full (depth more than 32). It rejects new work until the depth falls to 16. Send the request again after a short wait. |
Related Topics
- Stream Deck Plugin — The plugin that these routes drive
- REST API — The server, authentication, and name matching
- REST API Reference — Full interactive endpoint documentation
- OSC API Reference — The OSC address specification