WebSocket API Settings
Description
The WebSocket API allows external tools to control X-Creator and receive live presentation state over a persistent WebSocket connection, instead of polling. Once connected, the server pushes the full current state (experience list, and — once joined — the current experience, its slide list, its media pool, the active slide and that slide's live media layers) and then keeps the client up to date with events as the state changes, so a client never needs to poll for changes. Live layer edits (move, resize, rotate, add, remove, …) made on the X-Creator side are pushed without requiring a save.
The API is available at ws://<ip>:<port>/ws. Requests are JSON text messages; example of a valid connection URL: ws://192.168.0.1:9698/ws
Environment Configuration
Enable the WebSocket API module so it is available for the Environment.
X-Creator Configuration
Enable the WebSocket API module for one X-Creator in the Environment that should receive the WebSocket connections and use the IP of this X-Creator when connecting.
Port
The port on which connections are accepted (default 9698)
Authentication
Enables to define a Bearer Token that must be supplied when connecting. Since browsers cannot set custom headers on a WebSocket handshake, the token is passed as a query parameter on the connection URL instead of a header: ws://192.168.0.1:9698/ws?token=<token>. A connection with a missing or invalid token is sent {"type":"error","message":"Unauthorized"} and immediately closed.
API Documentation
Message Format
Every message, in both directions, is a single-line JSON object. Incoming frames larger than 1 MiB are rejected and the connection is closed.
Because of that this API carries no bulk or binary payloads, which is why downloading an X-Creator's log files exists only on the REST API — see REST API.
Commands (client → server)
{ "command": "slides/activate", "id": "<slideId>" }
command is required; id is only required by commands that operate on a specific slide, layer, media item or experience; name is only required by slides/rename; layer is only used by layers/update. The media control commands (see Media Control Commands), along with slides/timer and auto-forward/delay, use a handful of generic, reused argument fields instead of dedicated ones per command: value (number), intValue (int), boolValue (bool), text (string), color ({ "r", "g", "b", "a" }), and the strongly-typed videoPlayMode, webNavigation, ndiColorFormat, ndiBandwidth, textAlignment (each a string enum member). Which fields a command reads is documented per command below.
Responses (server → client)
{ "type": "response", "command": "slides/activate" }
Sent once per command, in reply to that command. Some commands include additional fields alongside command (see the command list below).
The slide navigation commands (slides/next, slides/previous, slides/activate) ask the server of the shared experience to change the active slide, so that all clients of that experience show the same slide. Their response confirms that the request was accepted and passed on, not that the slide has already changed. The server can decide not to carry a request out, for example when it arrives immediately after another slide change; no slide changes then, and the response is still a normal response rather than an error. Wait for the slide/changed event to see which slide actually became active.
Errors (server → client)
{ "type": "error", "command": "slides/activate", "message": "Slide 'abc' not found" }
Sent instead of a response when a command fails. command is omitted when the message itself could not be parsed (invalid JSON, or missing command field).
Events (server → client)
{ "type": "event", "event": "slide/changed", "name": "Intro", "id": "abc", "isFavorite": false }
Sent by the server on its own, without a preceding command, whenever presentation state changes. See "Events" below.
Connecting
Right after the WebSocket handshake completes, the server sends:
{"type":"event","event":"ready"}— signals that the server is now processing commands.experiences/listevent.clients/listevent.- If already joined to an experience:
experience/changed,slides/list,chapters/list,media/list,auto-forward/state, (if a slide is active)slide/changedandslides/current/layersevents.
The experiences/list and clients/list snapshot events are not gated on being joined to an experience — both are meaningful before that too. The rest of the set is pushed again whenever an experience is joined. A client can therefore rely entirely on this initial snapshot plus subsequent events and never has to send a command just to learn the current state.
Commands
| Command | id required | Description |
|---|---|---|
alive |
Checks that the server is responding. | |
slides/list |
Returns a list of all slides from the current experience, each with its name, ID, favorite flag and timerSeconds. |
|
slides/current |
Returns name, ID, favorite flag and timerSeconds of the current slide. |
|
slides/current/layers |
Returns the media layers of the currently active slide as Media layer objects. | |
slides/next |
Loads the next slide from the current experience. Commands arriving close together are collected into a single move covering all of them, so intermediate slides are skipped rather than each being loaded. | |
slides/previous |
Loads the previous slide from the current experience. Same collecting behavior as slides/next. |
|
slides/activate |
yes (slide ID) | Loads the slide with the given ID from the current experience. |
slides/layers |
yes (slide ID) | Returns the media layers of the slide with the given ID from the current experience as Media layer objects. |
slides/save |
Saves the currently active slide, capturing its current media layers and controls. | |
slides/rename |
yes (slide ID) + name |
Renames the slide with the given ID to name. |
slides/delete |
yes (slide ID) | Deletes the slide with the given ID. If it is the currently active slide, a neighboring slide is activated first. |
slides/timer |
yes (slide ID) + intValue |
Sets how long the slide with the given ID is shown before auto-forward moves on, in whole seconds. Zero removes the timer, the same as slides/timer/clear. No upper bound; a negative value is rejected. Setting the timer of the active slide restarts its countdown from the new duration. |
slides/timer/clear |
yes (slide ID) | Removes the timer from the slide with the given ID. The auto-forward delay still applies to it afterwards. |
chapters/list |
Returns a list of all chapters, each with its slides, from the current experience. See Chapter object. | |
chapters/get |
yes (chapter ID) | Returns the chapter with the given ID from the current experience. See Chapter object. |
chapters/activate |
yes (chapter ID) | Loads the first slide of the chapter with the given ID from the current experience. |
auto-forward/state |
Returns the auto-forward countdown of the current experience. See Auto-forward. | |
auto-forward/delay |
no, takes intValue |
Sets the auto-forward delay of the current experience in whole seconds, applying to every slide without a timer of its own. Zero switches auto-forward off. See Auto-forward. |
auto-forward/pause |
Pauses the running countdown. Errors if no countdown is running. | |
auto-forward/resume |
Resumes the paused countdown, which carries on from where it stopped. Errors if no countdown is running, or if the presentation is frozen. | |
media/list |
Returns every media item known to the currently joined experience, independent of slide or layer placement. | |
media/layer |
yes (media ID) | Creates a new media layer for the given media on the currently active slide, positioned near the center of the visible canvas; returns the created Media layer object. |
layers/get |
yes (layer ID) | Returns the media layer with the given ID on the currently active slide as a Media layer object, control state included. Refreshes one layer — including a playing video's position — without pulling the whole slide. |
layers/update |
yes (layer ID) + layer |
Applies a partial update to the media layer with the given ID on the currently active slide. The layer field holds a Media layer object with only the properties to change (see Updating a layer). |
layers/delete |
yes (layer ID) | Deletes the media layer with the given ID from the currently active slide, including its control state. |
experiences/list |
Returns a list of all experiences with names, IDs and if set as favorites. | |
experiences/current |
Returns name, ID, favorite flag and canvas dimensions of the current experience. | |
experiences/slides |
yes (experience ID) | Returns a list of all slides for the experience with the given ID, without joining it. If this X-Creator hasn't loaded that experience yet, it is fetched from X-Manager on demand. Only available on a X-Creator running the Host role. |
experiences/chapters |
yes (experience ID) | Returns a list of all chapters, each with its slides, for the experience with the given ID, without joining it. Same on-demand fetch and Host-role restriction as experiences/slides. See Chapter object. |
experiences/join |
yes (experience ID) | Joins the experience with the given ID. If the experience already has a client with sync enabled, this client joins with sync forced off, matching the app's own join behavior. |
experiences/leave |
Leaves the current experience. Errors if no experience is currently loaded. | |
clients/list |
Returns every connected client as Client objects. | |
clients/current |
Returns the client answering this request as a Client object. | |
clients/get |
yes (client ID) | Returns the client with the given ID as a Client object. |
x-manager/info |
Returns the X-Manager this X-Creator is connected to. See X-Manager info. | |
version/info |
Returns the running build. See Version info. | |
config/get |
Returns the X-Creator configuration this instance runs under. See Config info. | |
config/set |
no, takes configName + environmentName |
Selects the X-Creator configuration this instance runs under, taking effect on the next start; does not restart. Errors if either name is missing, if no configuration with that name exists in that environment, or if the X-Manager could not be reached to resolve them. See Config info. |
config/list |
Returns every X-Creator configuration the connected X-Manager offers — the names config/set can be given. Errors if the X-Manager could not be reached. See Config list. |
|
environment/list |
Returns every environment the connected X-Manager holds — where the environmentId the commands below take comes from. See Administrator commands. |
|
license/list |
Returns every license the connected X-Manager holds, without their keys. See Administrator commands. | |
config/environment |
yes (configuration ID), plus environmentId |
Moves an X-Creator configuration to another environment on the X-Manager. See Administrator commands. |
license/environment |
yes (license ID), plus environmentId |
Moves a license to another environment on the X-Manager. See Administrator commands. |
shortcuts/trigger |
no, takes shortcut |
Fires an app-side shortcut, exactly as pressing its key combination would. See Triggering a shortcut. |
shortcuts/get |
no, takes shortcut |
Returns the current state of a shortcut that has readable state. See Reading shortcut state. |
Media-type-specific operations (play/pause, page navigation, etc.) are listed separately in Media Control Commands.
Any unrecognized command value returns {"type":"error","command":"<command>","message":"Unknown command"}.
Response payloads
Successful responses carry their data alongside type and command as follows:
| Command | Payload |
|---|---|
slides/list |
array under slides |
slides/current/layers |
array under layers |
slides/layers |
array under layers, plus the requested id echoed |
chapters/list |
array under chapters |
chapters/get |
chapter object under chapter, plus the requested id echoed |
media/list |
array under media |
media/layer |
created layer object under layer |
layers/get |
layer object under layer, plus the requested id echoed |
experiences/list |
array under experiences |
experiences/slides |
array under slides, plus the requested id echoed |
experiences/chapters |
array under chapters, plus the requested id echoed |
slides/current |
name, id, isFavorite, timerSeconds inline on the envelope |
auto-forward/state |
slideId, running, durationMs, remainingMs, delaySeconds inline on the envelope |
experiences/current |
name, id, isFavorite, canvasWidth, canvasHeight inline on the envelope |
shortcuts/get |
enabled inline on the envelope |
clients/list |
array under clients |
clients/current |
Client object fields inline on the envelope |
clients/get |
client object under client, plus the requested id echoed |
x-manager/info |
url, version, publicFeatureEnabled inline on the envelope |
version/info |
version, branch, commit, buildTime, buildKind inline on the envelope |
config/get, config/set |
configId, configName, environmentId, environmentName, source inline on the envelope |
config/list |
array under configs |
environment/list |
array under environments |
license/list |
array under licenses |
config/environment |
configId, configName, environmentId, environmentName inline on the envelope |
license/environment |
licenseId, licenseName, environmentId, environmentName, isValid inline on the envelope |
All other commands (alive, navigation, save, rename, delete, join, leave, updates, shortcut triggers) reply with just { "type": "response", "command": "<command>" }.
Chapter object
The chapter commands and events (chapters/list, chapters/get, experiences/chapters, chapters/list event) describe a chapter with this object. A chapter is an ordered group of slides; every slide in an experience belongs to exactly one chapter.
| Field | Type | Description |
|---|---|---|
name |
string | Display name of the chapter. |
id |
string | Unique ID of the chapter. |
slides |
array | The chapter's slides, in order, each as { "name", "id", "isFavorite" } (same shape as slides/list, without timerSeconds). |
Media layer object
The layer commands and events (slides/current/layers, slides/layers, media/layer, layers/update, and the slides/current/layers event) describe each media layer with this object. Transform values are normalized rather than in pixels:
| Field | Type | Description |
|---|---|---|
mediaLayerId |
string | Unique ID of the layer. |
mediaId |
string | ID of the media the layer displays. |
mediaType |
string | Media type, e.g. Image, Video, Text, Web, AssetBundle, Shape. |
name |
string | Display name of the media. |
position |
{ "x", "y" } |
Normalized position. x spans the canvas width, y spans the canvas height (0–1 covers the canvas). |
size |
{ "x", "y" } |
Normalized size. Both x (width) and y (height) are relative to the canvas width. |
rotation |
number | Rotation in degrees. |
crop |
{ "x", "y", "z", "w" } |
Crop fractions (0–1) per edge: x=top, y=bottom, z=left, w=right. Also adjusts position/size to match, unless those are set in the same request. |
visible |
bool | Whether the layer is shown. |
alpha |
number | Opacity, 0–1. |
zOrder |
int | Stacking order; higher values are drawn in front. Relative only — not guaranteed 0-based or contiguous, and values differ between the active slide (normalized) and other slides (raw stored values). |
locked |
bool | Whether the layer is locked against edits. |
fixedAspectRatio |
bool | Whether width and height stay locked to the media's aspect ratio. |
fullscreen |
bool | Whether the layer fills the whole canvas. |
sectionFullscreen |
bool | Whether the layer fills its section. Mutually exclusive with fullscreen. |
backgroundColor |
{ "r", "g", "b", "a" } |
Background color behind the media, each channel 0–1. |
isStatic |
bool | Whether the layer is static (persists across every slide). Toggling this also re-maps layerGroup to/from its Static* variant. |
layerGroup |
string | Layer group: Background, Regular, Foreground, StaticBackground, StaticRegular, StaticForeground. Setting a non-static base group (e.g. Foreground) while the layer is static resolves to its Static* counterpart automatically. System-assigned groups (Widget, VideoSource, Audio) can't be set through the API. |
control |
object or null |
Media-type-specific control state snapshot, shaped per mediaType — see Media control object. null for media types with no control operations (e.g. Image, AssetBundle, VideoMatrix). |
Media control object
Every response/event that includes a Media layer object nests the layer's current control state under control, shaped according to its mediaType. To read it for a single layer, use the layers/get command.
mediaType |
Fields |
|---|---|
Video |
isPlaying (bool), playMode (string: Loop, ResetAfterPlay, PlayToEnd, ForwardSlideWhenFinished), volume (0–1), muted (bool), position (0–1, normalized over duration), duration (seconds) |
Audio |
isPlaying (bool), loop (bool), volume (0–1), muted (bool), position (0–1, normalized over duration), duration (seconds) |
duration is the media's total length in seconds, so position * duration gives the elapsed time — enough to show and seek to a real timecode. It is 0 when the length isn't known (media without duration metadata).
| Pdf | currentPage (int, 0-based), interaction (bool) |
| Web | webUrl (string) |
| Ndi | volume (0–1), muted (bool), colorFormat (string: BGRX_BGRA, UYVY_BGRA, RGBX_RGBA, UYVY_RGBA, Fastest), bandwidth (string: Highest, Lowest, AudioOnly) |
| Text | text (string), fontName (string), fontSize (number), fontColor ({ "r", "g", "b", "a" }), alignment (string, Unity TextAnchor member, e.g. UpperLeft), interaction (bool) |
| Shape | color ({ "r", "g", "b", "a" }) |
| everything else | null — no control operations are exposed for this media type |
Client object
The client commands and events (clients/list, clients/current, clients/get) describe a connected client with this object, sourced from the server's internal client-info state rather than a separate schema per client type:
| Field | Type | Description |
|---|---|---|
clientId |
string | Unique ID of the client. |
clientName |
string | Display name of the client, or empty if unset. |
clientType |
string | Client role, e.g. Visualisation, Control. |
connectionId |
int | Internal network connection handle, mainly useful for debugging. Not stable across reconnects. |
address |
string | Address this client can be reached at, as the host observes it on its connection. Empty on a client that has not registered with a host yet. Together with restPort this is what lets a caller address another client's own API without being told where it runs. |
restPort |
int | Port this client serves its REST API on, or 0 when the REST API is switched off for it (or has not started yet). |
syncEnabled |
bool | Whether this client follows another client's slide navigation. |
experienceId |
string | ID of the experience this client has joined, or empty if none. |
slideId |
string | ID of the slide this client currently has active, or empty if none. |
slideLoadState |
string | How far the client has got loading slideId, e.g. None, Loading, Settled. |
autoPlayDelayMs |
int | Configured auto-forward delay for this client in milliseconds, or 0 if auto-play is off. |
downloadProgress |
int | Media download progress, 0–100. |
ndi |
object | enabled (bool), mediaIds (array of string) — whether NDI is enabled on this client and which media it currently sources over NDI. |
hardwareInfo |
object | deviceType (string, Unity DeviceType member), deviceName (string), systemMemorySize (int, MB), processorFrequency (int, MHz), graphicsMemorySize (int, MB). |
The list is only as current as the server's own broadcast — a Control client reads the same continuously-synced copy as the host, so the commands and the clients/list event work the same regardless of which client answers.
X-Manager info
The x-manager/info command describes the X-Manager this X-Creator is connected to with these fields:
| Field | Type | Description |
|---|---|---|
url |
string | Address of the X-Manager as configured in X-Creator, a full base URL with scheme, host and port (e.g. https://x-manager.example.com:3001) rather than a bare IP. |
version |
string | Release version of the X-Manager, e.g. 2023.2.279 (develop). Empty until the X-Manager has been reachable at least once. |
publicFeatureEnabled |
bool | Whether the public feature is enabled on the X-Manager. Read from the X-Manager's own settings, not inferred from whether X-Creator currently holds a public token. false until the X-Manager has been reachable at least once. |
version and publicFeatureEnabled are read from the X-Manager when the backend connection comes up and re-read on every reconnect, so a setting changed on the X-Manager is picked up without restarting X-Creator. Every X-Creator reads them for itself, so the answer does not depend on which instance is asked.
Version info
The version/info command describes the running build with these fields:
| Field | Type | Description |
|---|---|---|
version |
string | Application version including the build number, e.g. 2023.2.279. |
branch |
string | Branch the build came from, e.g. develop or staging. Empty for a production build, and for a locally made build, which carries no build manifest. |
commit |
string | Commit the build came from. Empty when no build manifest is present. |
buildTime |
string | When the build was made. Empty when no build manifest is present. |
buildKind |
string | production, or agent-api for a build carrying the extra unauthenticated agent interface. Decided at compile time, so a production build answers this too rather than the question only being answerable where that interface exists. |
Config info
The config/get and config/set commands describe the X-Creator configuration this instance runs under with these fields. Everything here is per instance: each X-Creator is served its own configuration, so ask the instance you mean rather than the host.
| Field | Type | Description |
|---|---|---|
configId |
string | ID of the X-Creator configuration, as registered on the X-Manager. Reflects whatever was last selected (welcome screen or config/set), not re-fetched by this command. |
configName |
string | Name of that configuration. This is the same value the Client object reports as clientName. Same caveat as configId. |
environmentId |
string | ID of the environment the configuration belongs to. |
environmentName |
string | Name of that environment. Empty until the X-Manager has served this instance its client info at least once, since only the X-Manager knows the name behind the id. |
source |
string | Whether environmentId/environmentName came from the X-Manager this run: x-manager when it served them, cached when it could not be reached and the previous run's values were kept, unknown before either has happened. Does not describe configId/configName, which this command never re-fetches. |
source is worth reading before trusting the environment fields: startup deliberately carries on with the previous run's environment info when the X-Manager is unreachable, and it looks no different from a freshly-served value once in place. Since both requests are keyed only by client id, resolved independently server-side, a cached environment is not just possibly stale but possibly wrong for what this instance is actually running — the X-Manager can have reassigned this client to a different environment in the meantime, and the license/config the app is running under would already reflect that even though environmentName here still doesn't.
config/set selects by name rather than by id, because the X-Manager mints new ids whenever its content is reimported while names stay put. environmentName is required as well, since configuration names are only unique within one environment. It takes effect on the next start and deliberately does not restart the application — nothing is torn down at the moment of the call, and until the restart happens the instance keeps running under its previous configuration while reporting the new one.
{ "command": "config/set", "configName": "Visualisation Left", "environmentName": "Showroom" }
Config list
The config/list command returns every X-Creator configuration the connected X-Manager offers, as an array under configs. Unlike Config info this says nothing about what the asked instance is running — it is the list to pick from, not a report about the instance.
| Field | Type | Description |
|---|---|---|
configId |
string | ID of the X-Creator configuration, as registered on the X-Manager. Not accepted by config/set, and not stable across a reimport. |
configName |
string | Name of that configuration. This, paired with environmentName, is what config/set selects by. |
environmentId |
string | ID of the environment the configuration belongs to. |
environmentName |
string | Name of that environment. Needed alongside configName to address a configuration, since names are only unique within one environment. |
The list comes from the X-Manager itself rather than from anything held locally, so this needs it to be reachable and errors when it is not.
Administrator commands
config/environment and license/environment do not change this instance but the X-Manager's own records, so that an automated setup can move an X-Creator between environments without anyone opening the X-Manager UI.
Because of that they need more than this API's bearer token: the X-Creator forwards them to the X-Manager under the credential of the user logged in on this instance, and the X-Manager only accepts them from a member of its Administrators group. There is no second credential to fall back on, so an instance that is not logged in as such a user returns an error naming which of these applies: it accesses the X-Manager with an API token, which is minted without administrator rights; no X-Manager user is logged in on it, including when it runs in public mode; or the logged-in user is not in the Administrators group. The X-Manager can also refuse the change itself after that check passed, for instance when the user's group membership changed since the login.
Both address their target by id and the environment by environmentId, because the X-Manager identifies these records by id rather than by name. None of these ids survive a reimport of the X-Manager's content, so they are read fresh:
environment/listreturns every environment asenvironmentId+environmentNameunderenvironments. This is how an environment name becomes an id, and it reaches environments thatconfig/listcannot — that one only shows environments which already have a configuration, which is exactly what a configuration is usually moved away from.license/listreturns every license underlicenses, with the fields below and never a key. It also shows which environment currently holds the license.
Both lists are plain reads and need no administrator credential of their own, only whatever the X-Manager requires to serve them.
config/environment moves an X-Creator configuration to another environment. Instances running that configuration are served the new environment's settings from their next start. A configuration belongs to exactly one environment, which is why this is a separate command rather than a field on config/set. The response repeats the Config list fields. environmentName is resolved separately, because the X-Manager's answer to this command names the environment by id only; it is null if that lookup failed — the move itself has already happened by then and is not reported as failed over it.
{ "command": "config/environment", "id": "6634f1b2c8a94d0012ab9f01", "environmentId": "6634f1b2c8a94d0012ab34cd" }
license/environment moves a license to another environment. An environment without a license cannot be started on: an X-Creator moved to it fails during startup with an invalid-license error rather than a configuration error. A license record carries one environment, so this re-points it rather than copying it: the environment it was on loses this license, and whether that leaves it unlicensed depends on whether another license still covers it. A trial is the exception — there is only one per installation, so it cannot cover two environments and has to be moved between them. The license does not have to be deactivated first, and its key is never part of the request or the response.
{ "command": "license/environment", "id": "6634f1b2c8a94d0012abcd77", "environmentId": "6634f1b2c8a94d0012ab34cd" }
| Field | Type | Description |
|---|---|---|
licenseId |
string | ID of the license on the X-Manager. |
licenseName |
string | Name of that license. |
environmentId |
string | ID of the environment the license now applies to. |
environmentName |
string | Name of that environment. Looked up separately when the license document does not carry it, and null if that lookup failed. |
isValid |
boolean | Whether the license covers the environment it now belongs to. false does not mean the change failed — the move succeeds either way, but an X-Creator on that environment then fails to start with an invalid-license error. |
Auto-forward
Auto-forward moves the presentation on by itself. How long a slide is shown is resolved from two settings: the slide's own timerSeconds if it has one, otherwise the experience's auto-forward delay, which applies to every slide without a timer.
The auto-forward/state response and event carry the countdown:
| Field | Type | Description |
|---|---|---|
slideId |
string | The slide the countdown is running for, empty when none is running. |
running |
bool | Whether the countdown is running rather than paused. |
durationMs |
int | How long the countdown runs in total. |
remainingMs |
int | How much was left when the event was sent. |
delaySeconds |
number | The auto-forward delay, 0 when auto-forward is off. Fractional when it comes from a client configuration that is not a whole number of seconds. |
The countdown does not tick over the network — auto-forward/state is only pushed when it starts, stops, is paused or resumed, or when the delay changes. A client wanting a live readout counts remainingMs down against its own clock from the moment the event arrived, exactly as the app's own timer ring does.
Pause and resume are unowned: a countdown paused through the API is resumed by the app's own timer ring just as readily as by auto-forward/resume, and vice versa. Naming the operation rather than toggling also makes it idempotent — sending auto-forward/pause twice succeeds and changes nothing. Pausing is allowed while the presentation is frozen (freeze already holds the countdown, and pausing on top of it is what keeps it from resuming when freeze is lifted); resuming while frozen is rejected.
auto-forward/delay overrides the delay taken from client configuration for as long as the server runs; a restart falls back to client configuration, and nothing is written to any configuration file. Zero switches auto-forward off — there is no separate enable/disable and no remembered previous value. A countdown already running for a slide without its own timer restarts from the new delay, as it does when that slide's own timer is changed.
There is no restart command: reloading the slide restarts its countdown.
Media Control Commands
All commands below take id (a mediaLayerId) and reply with just { "type": "response", "command": "<command>" } on success, or an error using the same conditions as other layer commands (Missing id, Media layer '<id>' not found, or a "has no X control state" message when the layer's mediaType doesn't support the operation).
Scope — some operations apply only to the targeted layer instance ("Layer"); others apply to every layer currently showing the same media item ("Media"), e.g. muting one placement of a video mutes all of them. A control-state change (from any client) also triggers a slides/current/layers event, same as a transform edit.
Volume and mute — the video/volume, audio/volume, and ndi/volume commands also set mute, mirroring the volume slider in the app: a volume above 0 unmutes, a volume of 0 mutes. The app's slider reads as 0 for as long as mute is set, whatever volume is behind it, so a volume change that left mute untouched would not be visible there. Send */mute after */volume if the two need to be set independently.
Video (all Media-scoped)
| Command | Fields | Description |
|---|---|---|
video/play |
boolValue (isPlaying) |
Play or pause at the media's current position; never seeks. |
video/seek |
value (position) |
Seek without changing play state. 0–1, normalized over the media's duration. |
video/mode |
videoPlayMode |
Loop, ResetAfterPlay, PlayToEnd, or ForwardSlideWhenFinished. |
video/volume |
value |
0–1. Also unmutes, or mutes at 0. |
video/mute |
boolValue |
Audio (all Layer-scoped)
| Command | Fields | Description |
|---|---|---|
audio/play |
boolValue (play) |
Play or pause at the media's current position; never seeks. |
audio/position |
value |
Seek. 0–1, normalized over the media's duration. |
audio/loop |
boolValue |
|
audio/volume |
value |
0–1. Also unmutes, or mutes at 0. |
audio/mute |
boolValue |
Pdf (all Layer-scoped)
| Command | Fields | Description |
|---|---|---|
pdf/page |
intValue (page) |
Jump to a 0-based page number. |
pdf/next-page |
none | Advance one page, clamped to the last page. |
pdf/previous-page |
none | Go back one page, clamped to page 0. |
pdf/first-page |
none | Jump to page 0. |
pdf/last-page |
none | Jump to the last page. |
pdf/interaction |
boolValue |
Toggles direct interaction (scrolling) with the PDF on the layer. |
pdf/enable-scrolling |
none | Re-enables dispatching the current page while scrolling manually. |
Web (Layer-scoped)
| Command | Fields | Description |
|---|---|---|
web/url |
text (url) |
Loads a new URL. |
web/navigate |
webNavigation |
Back, Forward, or Reload. |
Ndi (all Media-scoped)
| Command | Fields | Description |
|---|---|---|
ndi/volume |
value |
0–1. Also unmutes, or mutes at 0. |
ndi/mute |
boolValue |
|
ndi/color-format |
ndiColorFormat |
BGRX_BGRA, UYVY_BGRA, RGBX_RGBA, UYVY_RGBA, or Fastest. |
ndi/bandwidth |
ndiBandwidth |
Highest, Lowest, or AudioOnly. |
Text
| Command | Scope | Fields | Description |
|---|---|---|---|
text/text |
Media | text |
|
text/font |
Layer | text (fontName) |
|
text/font-size |
Layer | value (fontSize) |
|
text/font-color |
Layer | color |
|
text/alignment |
Layer | textAlignment |
Unity TextAnchor member, e.g. UpperLeft, MiddleCenter. |
text/interaction |
Layer | boolValue |
Shape (Layer-scoped)
| Command | Fields | Description |
|---|---|---|
shape/color |
color |
Not exposed: freeze (any media type), NDI PTZ (ndi/ptz/*) and camera presets, Web scale/interaction/interaction-authority, and AssetBundle/VideoMatrix controls entirely.
To convert normalized values to pixels, use the canvasWidth / canvasHeight fields from the experience/changed event or experiences/current response (default 1920×1080):
xPx = position.x * canvasWidth, yPx = position.y * canvasHeight, widthPx = size.x * canvasWidth, heightPx = size.y * canvasWidth.
Updating a layer
layers/update carries a layer object containing only the properties to change; omitted properties are left untouched. At least one property must be present, otherwise the command errors with Missing layer properties.
{ "command": "layers/update", "id": "<layerId>", "layer": { "position": { "x": 0.25, "y": 0.5 } } }
Notes:
position,sizeandcropare sent whole (all components), not per-axis.rotationis an absolute value in degrees; the server applies the difference from the current rotation.- Setting
fullscreentotrueclearssectionFullscreenand vice versa (they are mutually exclusive). - Errors:
Missing id,No active slide,Media layer '<id>' not found,Missing layer properties, orLayer group '<value>' cannot be set directly(system-assigned or unknownlayerGroup).
Triggering a shortcut
shortcuts/trigger takes a shortcut name instead of an id and fires that shortcut's handlers, exactly as pressing its key combination on the X-Creator would. No experience needs to be joined.
{ "command": "shortcuts/trigger", "shortcut": "MouseCursor" }
Only these three shortcuts are exposed:
shortcut |
Key combination | Effect |
|---|---|---|
MouseCursor |
Ctrl + M |
Shows or hides the mouse cursor. |
ShowWelcomeScreen |
Ctrl + W |
Shows or hides the welcome screen. |
ForceWindowFocus |
Alt + Shift + W |
Turns forcing focus on the X-Creator window on or off. |
MouseCursor and ShowWelcomeScreen are toggles that report no state, so this command can only flip them. ForceWindowFocus is the exception — read its resulting state back with Reading shortcut state below.
Errors: Missing or unknown shortcut, or Shortcut '<name>' cannot be triggered remotely for a valid shortcut that is not in the list above.
Reading shortcut state
shortcuts/get takes a shortcut name and returns its current state. Only ForceWindowFocus supports this — MouseCursor and ShowWelcomeScreen are pure toggles with nothing to read back.
{ "command": "shortcuts/get", "shortcut": "ForceWindowFocus" }
Response payload: enabled (boolean), inline on the envelope.
Errors: Only 'ForceWindowFocus' has readable state for any other shortcut name, or Force window focus watcher is not active on this instance when the watcher isn't installed (editor, non-Windows, or server role).
Events
Events are pushed by the server without a matching command; a client should apply them to its local state as they arrive.
| Event | Description |
|---|---|
ready |
Sent once right after connecting; the state snapshot described above follows immediately after. |
experiences/list |
The list of accessible experiences. Sent in the connect snapshot and re-sent whenever the list changes. |
clients/list |
The list of connected clients as Client objects. Sent in the connect snapshot and re-sent whenever any client's info changes — not gated on being joined to an experience. |
experience/state |
Informational only; state is joining or leaving while a join/leave is in progress. Does not drive client state by itself. |
experience/changed |
Sent once an experience join completes, together with the rest of the joined-experience snapshot. Includes canvasWidth and canvasHeight (pixels) for converting normalized layer values. |
experience/left |
Sent once the client has left its experience. |
slides/list |
The slide list of the currently joined experience; sent on join and re-sent whenever a slide is added, removed, renamed or otherwise changes. |
chapters/list |
The chapter list of the currently joined experience, each with its slides; sent on join and re-sent whenever a chapter is added, removed, renamed, or its slides change. See Chapter object. |
slide/changed |
Sent whenever the active slide changes, including right after a join completes. |
media/list |
The media pool of the currently joined experience; sent on join and re-sent whenever media is added or removed. |
auto-forward/state |
The auto-forward countdown; sent on join and on every transition, never on a tick. See Auto-forward. |
slides/current/layers |
The active slide's media layers as Media layer objects; sent on join, on slide change, and live as layers are edited. Continuous edits (e.g. a drag) are coalesced to at most one event per second. |
Samples
Connect and load the next slide:
{ "command": "slides/next" }
Get the media layers of the current slide:
{ "command": "slides/current/layers" }
Rename a slide:
{ "command": "slides/rename", "id": "<slideId>", "name": "New name" }
Move a layer and set its opacity (only the given properties change):
{ "command": "layers/update", "id": "<layerId>", "layer": { "position": { "x": 0.5, "y": 0.5 }, "alpha": 0.8 } }
Tools
Built-in Test Console
Opening http://<ip>:<port>/ (the same host and port as the WebSocket endpoint) in a browser serves a built-in interactive console for connecting, sending commands and inspecting responses/events without any external tooling.
Troubleshooting
Connection is closed immediately after connecting
If Authentication is enabled, the connection URL is missing a valid ?token=<token> query parameter. Check the Bearer Token configured for the X-Creator.
Command returns {"type":"error", ...}
The message field describes why the command could not be completed, e.g. a slide or experience ID that does not exist, or navigating past the last/first slide. This mirrors the equivalent REST API error conditions.
No events are received after connecting
Confirm the ready event was received first — if not, the handshake or authentication failed. If ready was received but no experience-specific events follow, no experience is currently joined; join one with experiences/join first.