2023.2 EN DE

    Show / Hide Table of Contents

    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.

    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:

    1. {"type":"event","event":"ready"} — signals that the server is now processing commands.
    2. experiences/list event.
    3. clients/list event.
    4. If already joined to an experience: experience/changed, slides/list, media/list, auto-forward/state, (if a slide is active) slide/changed and slides/current/layers events.

    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.
    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/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.
    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
    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
    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

    All other commands (alive, navigation, save, rename, delete, join, leave, updates, shortcut triggers) reply with just { "type": "response", "command": "<command>" }.

    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.
    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.

    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, size and crop are sent whole (all components), not per-axis.
    • rotation is an absolute value in degrees; the server applies the difference from the current rotation.
    • Setting fullscreen to true clears sectionFullscreen and vice versa (they are mutually exclusive).
    • Errors: Missing id, No active slide, Media layer '<id>' not found, Missing layer properties, or Layer group '<value>' cannot be set directly (system-assigned or unknown layerGroup).

    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.
    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.

    
    ☀
    ☾
    Back to top
    Generated by DocFX
    ☀
    ☾