Skip to content

WebSocket API Reference

MuxitServer exposes a WebSocket server for browser clients. All messages are JSON objects with a type field.

Connection

ws://localhost:8765/ws?token=<auth-token>
wss://localhost:8765/ws?token=<auth-token>  (when HTTPS enabled)

Default port is 8765, configurable in workspace/config/server.json.

Authentication

Local Access (Loopback)

The UI auto-fetches the auth token via GET /api/auth/token (loopback-only endpoint). No user interaction needed.

Remote Access

When security.remoteAccess is enabled and a password is set:

  1. GET /api/auth/token returns 403 for non-loopback requests
  2. GET /api/auth/status returns { requiresLogin: true, ... }
  3. POST /api/auth/login with { "password": "..." } returns { "token": "<auth-token>", "sessionToken": "<session>" }
  4. Use the auth token for WebSocket connections (?token=...) and HTTP API calls (X-Auth-Token header)

HTTP API Endpoints

EndpointMethodAuthDescription
/api/auth/tokenGETLoopback onlyGet startup auth token
/api/auth/statusGETNoneCheck auth requirements
/api/auth/loginPOSTNoneAuthenticate with password
/api/auth/passwordPOSTLocal or authenticatedSet/update access password
/api/auth/passwordDELETELocal onlyRemove access password

Rate Limiting

Login attempts are limited to 5 per minute per IP. Returns HTTP 429 with { "retryAfter": <seconds> }.


Message Format

Request (client → server)

json
{
  "type": "message.type",
  "requestId": "optional-correlation-id",
  ...
}

Response (server → client)

json
{
  "type": "response.type",
  "requestId": "echoed-if-provided",
  "value": ...
}

Error

json
{
  "type": "error",
  "code": "CONN_NOT_FOUND",
  "requestId": "echoed-if-provided",
  "message": "Human-readable error description"
}

The code field is a structured error code (UPPER_SNAKE_CASE) for programmatic error handling. See Error Codes for the full list.


Error Codes

All error responses include a code field from the table below. Use these codes for programmatic error handling and when reporting issues.

Protocol Errors

CodeDescription
PROTOCOL_INVALID_JSONMessage could not be parsed as JSON
PROTOCOL_MISSING_TYPEMessage has no type field
PROTOCOL_UNKNOWN_TYPEMessage type does not match any handler

Validation Errors

CodeDescription
VALIDATION_MISSING_PARAMA required parameter is missing from the request
VALIDATION_INVALID_VALUEA parameter has an invalid value

Connector Errors

CodeDescription
CONN_NOT_FOUNDThe specified connector does not exist
CONN_NOT_INITIALIZEDThe connector exists but has not been initialized
CONN_LOAD_FAILEDThe connector config file failed to parse or load
CONN_CALL_FAILEDA method call on the connector failed
CONN_DISABLEDThe connector is disabled
CONN_LIMIT_REACHEDLicense tier connector limit exceeded
CONN_RELOAD_FAILEDHot-reload of connectors failed

Driver Errors

CodeDescription
DRIVER_NOT_FOUNDThe specified driver does not exist in the registry
DRIVER_LOAD_FAILEDThe driver DLL/assembly failed to load
DRIVER_BLOCKEDThe driver requires a license that is not active

Script Errors

CodeDescription
SCRIPT_NOT_FOUNDThe script file was not found
SCRIPT_ALREADY_RUNNINGA script with this name is already running
SCRIPT_EXEC_FAILEDThe script threw an error during V8 execution
SCRIPT_LIMIT_REACHEDLicense tier concurrent script limit exceeded

Agent Errors

CodeDescription
AGENT_NOT_FOUNDThe agent config or instance was not found
AGENT_LIMIT_REACHEDMaximum concurrent agents limit exceeded
AGENT_SAFETY_BLOCKEDThe safety gate blocked the action (rate, workspace, speed, force)
AGENT_DEVICE_MISSINGA required device for the agent is not available
AGENT_AI_NOT_CONFIGUREDNo AI provider configured for the agent
AGENT_START_FAILEDThe agent failed to start

License Errors

CodeDescription
LICENSE_LIMITA generic license tier limit was exceeded
LICENSE_ACTIVATION_FAILEDLicense key activation failed
LICENSE_EXPIREDThe license subscription has expired
LICENSE_DEACTIVATION_FAILEDLicense deactivation failed

Auth Errors

CodeDescription
AUTH_RATE_LIMITEDToo many login attempts — try again later
AUTH_INVALID_SESSIONThe session token is invalid or expired
AUTH_INVALID_PASSWORDThe provided password is incorrect

Config Errors

CodeDescription
CONFIG_READ_FAILEDFailed to read a configuration file
CONFIG_WRITE_FAILEDFailed to write a configuration file
CONFIG_INVALIDThe configuration data is invalid

AI Errors

CodeDescription
AI_NOT_CONFIGUREDNo AI API key or provider is configured
AI_REQUEST_FAILEDThe AI/LLM API request failed

Internal Errors

CodeDescription
INTERNAL_ERRORAn unexpected internal error occurred

Connector Messages

connectors.list

List all loaded connector names.

json
// Request
{ "type": "connectors.list", "requestId": "1" }
// Response
{ "type": "connectors.list", "requestId": "1", "value": ["psu", "robot", "panel"] }

connector.schema

Get the schema for a single connector.

json
// Request
{ "type": "connector.schema", "requestId": "2", "connector": "psu" }
// Response
{
  "type": "connector.schema", "requestId": "2",
  "value": {
    "name": "psu", "driver": "MockInstrument",
    "properties": { "voltage": { "type": "number", "access": "rw", "unit": "V" } },
    "actions": { "reset": { "description": "Reset to defaults" } },
    "streams": []
  }
}

connectors.schema

Get schemas for all connectors at once (only enabled/loaded connectors).

json
// Request
{ "type": "connectors.schema", "requestId": "3" }
// Response
{ "type": "connectors.schema", "requestId": "3", "value": { "psu": {...}, "robot": {...} } }

connectors.all

Get metadata for ALL discovered connectors (enabled + disabled).

json
// Request
{ "type": "connectors.all", "requestId": "4" }
// Response
{
  "type": "connectors.all", "requestId": "4",
  "value": {
    "enabled": [
      { "name": "test-device", "driver": "TestDevice", "isTestDevice": true },
      { "name": "bk-psu", "driver": "GenericScpi", "isTestDevice": false }
    ],
    "disabled": [
      { "name": "webcam", "driver": "Webcam", "isTestDevice": false, "reason": "Not enabled (limit: 3 connectors)" }
    ],
    "maxConnectors": 3,
    "enabledNames": ["test-device", "bk-psu"],
    "enabledNonTestCount": 1,
    "tierName": "Free"
  }
}

connectors.set_enabled

Update which connectors are enabled. Validates against license limits.

json
// Request
{ "type": "connectors.set_enabled", "requestId": "5", "connectors": ["bk-psu", "camera", "robot"] }
// Response (success)
{ "type": "connectors.set_enabled", "requestId": "5", "success": true, "requiresRestart": true }

connectors.reload

Hot-reload all connectors without server restart.

json
{ "type": "connectors.reload", "requestId": "6" }

connector.call

Call a property (get/set) or action on a connector.

json
// Read property
{ "type": "connector.call", "requestId": "4", "connector": "psu", "property": "voltage" }

// Write property
{ "type": "connector.call", "requestId": "5", "connector": "psu", "property": "voltage", "args": [12] }

// Execute action
{ "type": "connector.call", "requestId": "6", "connector": "psu", "method": "reset" }

// Response
{ "type": "connector.response", "requestId": "4", "value": 12.0 }

Driver Messages

drivers.list

json
{ "type": "drivers.list", "requestId": "8" }
// Response
{ "type": "drivers.list", "requestId": "8", "value": [
  { "name": "TestDevice", "tier": 0, "version": "1.0.0", "description": "Simulated test device...", "category": "built-in", "group": "utilities" },
  { "name": "Fairino", "tier": 3, "version": "2.0.0", "description": "Fairino robot driver", "category": "free", "group": "motion" }
]}

driver.schema

Get the full API schema for a driver, including all properties, actions with argument details, streams, and the connector template.

json
{ "type": "driver.schema", "requestId": "9", "name": "TestDevice" }
// Response
{ "type": "driver.schema", "requestId": "9", "name": "TestDevice", "value": {
  "name": "TestDevice",
  "version": "1.0.0",
  "description": "Simulated test device for development...",
  "category": "built-in",
  "tier": 0,
  "properties": [
    { "name": "temperature", "type": "double", "access": "R/W", "unit": "°C", "description": "Current temperature" }
  ],
  "actions": [
    { "name": "setThreshold", "description": "Set the alert threshold", "args": [
      { "name": "value", "type": "double", "description": "Threshold value (0-100)" }
    ]}
  ],
  "streams": ["data"],
  "connectorTemplate": "export default { ... }"
}}

driver.template

Get the connector config template for a driver.

json
{ "type": "driver.template", "requestId": "10", "name": "TestDevice" }

Stream Messages

stream.subscribe / stream.unsubscribe

json
{ "type": "stream.subscribe", "connector": "spectrometer", "stream": "spectrum" }
{ "type": "stream.unsubscribe", "connector": "spectrometer", "stream": "spectrum" }

stream.data (server → client)

json
{
  "type": "stream.data",
  "name": "spectrometer", "stream": "spectrum",
  "data": { "wavelengths": [380, 381, ...], "intensities": [0.1, 0.2, ...] }
}

For image streams (webcam), data is a base64-encoded JPEG string.


State Messages

state.subscribe

Subscribe to reactive state updates. Immediately receives current state, then state.batch on changes.

json
{ "type": "state.subscribe" }
// Immediate response
{ "type": "state.batch", "updates": { "psu": { "voltage": 12 }, "robot": { "position": [100, 200, 300] } } }

state.snapshot

One-shot state read without ongoing subscription.

json
{ "type": "state.snapshot", "requestId": "20" }

state.batch (server → client)

Pushed every 50ms when polled property values change (delta broadcasting).

json
{ "type": "state.batch", "updates": { "psu": { "voltage": 12.1, "power": 6.05 } } }

Script Messages

scripts.list

json
{ "type": "scripts.list", "requestId": "12" }
// Response
{ "type": "scripts.list", "requestId": "12", "value": ["hello", "monitor"] }

scripts.start

json
// By name
{ "type": "scripts.start", "requestId": "13", "name": "my-script" }
// With inline code
{ "type": "scripts.start", "requestId": "13", "name": "my-script", "code": "log.info('Hello!')" }

scripts.execute

Execute synchronously — waits for completion and returns output.

json
{ "type": "scripts.execute", "requestId": "15", "name": "my-script", "code": "log.info('Hello!'); 42" }
// Response
{
  "type": "scripts.result", "requestId": "15", "name": "my-script",
  "result": 42,
  "logs": [{ "level": "info", "args": ["Hello!"] }],
  "error": null
}

scripts.stop

json
{ "type": "scripts.stop", "requestId": "14", "name": "my-script" }

script.say (broadcast)

json
{ "type": "script.say", "script": "temp-monitor", "text": "Temperature is 25.3 degrees" }

Agent Messages

agent.list

json
{ "type": "agent.list", "requestId": "..." }
// Response
{
  "type": "agent.list",
  "configs": [{ "name": "pick-and-place", "description": "...", "devices": ["robot", "camera"], "autonomy": "supervised" }],
  "running": [{ "agentId": "abc123", "name": "pick-and-place", "goal": "...", "status": "executing" }]
}

agent.start

json
{ "type": "agent.start", "name": "pick-and-place", "goal": "Pick up the red part", "parameters": { "partColor": "red" }, "autonomy": "supervised" }
// Response
{ "type": "agent.started", "agentId": "abc123", "name": "pick-and-place", "goal": "Pick up the red part" }

agent.stop / agent.pause / agent.resume

json
{ "type": "agent.stop", "agentId": "abc123" }
{ "type": "agent.pause", "agentId": "abc123" }
{ "type": "agent.resume", "agentId": "abc123" }

agent.approve / agent.deny

json
{ "type": "agent.approve", "agentId": "abc123", "stepId": "step-id" }
{ "type": "agent.deny", "agentId": "abc123", "stepId": "step-id" }

agent.state (broadcast)

json
{
  "type": "agent.state",
  "data": {
    "agentId": "abc123def456",
    "name": "pick-and-place",
    "goal": "Pick up the red part",
    "status": "executing",
    "iteration": 5,
    "planSteps": [
      { "id": "a1b2c3", "description": "Moving to pick position", "status": "executing" },
      { "id": "d4e5f6", "description": "Close gripper", "status": "pending" }
    ]
  }
}

Config Messages

config.get / config.set

json
{ "type": "config.get", "requestId": "30" }

// Dot-path update
{ "type": "config.set", "requestId": "31", "path": "ai.provider", "value": "claude" }
// Patch update
{ "type": "config.set", "requestId": "31", "patch": { "ai": { "safetyMode": "trust" } } }

AI Messages

Client → Server

TypeFieldsDescription
ai.chatsessionId, message, ttsEnabled?Send user message, triggers agentic loop
ai.tool_approverequestIdApprove a pending tool call
ai.tool_denyrequestIdDeny a pending tool call

Server → Client (Streamed)

TypeFieldsDescription
ai.deltacontent, sessionIdStreaming text chunk
ai.tool_calltool, input, resultTool executed
ai.tool_pendingrequestId, tool, inputTool awaiting approval
ai.imagetool, image, sessionIdCamera snapshot captured
ai.chatmessage, toolCallsFinal response

License Messages

license.get

Returns current license state including tier, limits, trials, and usage.

license.activate

Activate a license key. For base subscriptions, driver entitlements included as subscription add-ons are automatically synced from Lemon Squeezy — no separate driver keys needed. Standalone per-driver keys are also supported as a fallback.

json
{ "type": "license.activate", "licenseKey": "XXXX-XXXX-XXXX-XXXX" }

license.deactivate_driver

Deactivate a standalone per-driver license. Subscription-managed driver entitlements cannot be deactivated individually — manage them from the Lemon Squeezy subscription portal instead.

json
{ "type": "license.deactivate_driver", "driverId": "my-driver" }

license.start_trial

json
{ "type": "license.start_trial", "driverId": "optional-driver-id" }

license.changed (broadcast)

Broadcast to all clients whenever license state changes.


Server Log Messages

server.logs.history

Fetch buffered log history (last 500 entries).

server.log (broadcast)

Real-time server log entry.

json
{ "type": "server.log", "level": "info", "source": "connectors", "message": "Connector 'psu' initialized", "time": "..." }

Update Messages

update.check

Check for available updates. Returns current and latest version info.

Response:

json
{
  "type": "update.status",
  "value": {
    "currentVersion": "0.1.0",
    "latestVersion": "0.2.0",
    "updateAvailable": true,
    "downloadUrl": "https://github.com/muxit-io/muxit/releases/download/v0.2.0/muxit-win-x64.zip"
  }
}

update.available (broadcast)

Emitted on startup when a newer version is available.

json
{ "type": "update.available", "current": "0.1.0", "latest": "0.2.0", "downloadUrl": "..." }

Example: JavaScript Client

javascript
const ws = new WebSocket("ws://localhost:8765/ws?token=YOUR_TOKEN");

ws.onopen = () => {
  ws.send(JSON.stringify({ type: "state.subscribe" }));
  ws.send(JSON.stringify({ type: "connector.call", requestId: "1", connector: "psu", property: "voltage" }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type === "state.batch") console.log("State:", msg.updates);
  else if (msg.type === "connector.response") console.log(`[${msg.requestId}]:`, msg.value);
};

Muxit — Hardware Orchestration Platform