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:
GET /api/auth/tokenreturns 403 for non-loopback requestsGET /api/auth/statusreturns{ requiresLogin: true, ... }POST /api/auth/loginwith{ "password": "..." }returns{ "token": "<auth-token>", "sessionToken": "<session>" }- Use the auth token for WebSocket connections (
?token=...) and HTTP API calls (X-Auth-Tokenheader)
HTTP API Endpoints
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/auth/token | GET | Loopback only | Get startup auth token |
/api/auth/status | GET | None | Check auth requirements |
/api/auth/login | POST | None | Authenticate with password |
/api/auth/password | POST | Local or authenticated | Set/update access password |
/api/auth/password | DELETE | Local only | Remove 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)
{
"type": "message.type",
"requestId": "optional-correlation-id",
...
}Response (server → client)
{
"type": "response.type",
"requestId": "echoed-if-provided",
"value": ...
}Error
{
"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
| Code | Description |
|---|---|
PROTOCOL_INVALID_JSON | Message could not be parsed as JSON |
PROTOCOL_MISSING_TYPE | Message has no type field |
PROTOCOL_UNKNOWN_TYPE | Message type does not match any handler |
Validation Errors
| Code | Description |
|---|---|
VALIDATION_MISSING_PARAM | A required parameter is missing from the request |
VALIDATION_INVALID_VALUE | A parameter has an invalid value |
Connector Errors
| Code | Description |
|---|---|
CONN_NOT_FOUND | The specified connector does not exist |
CONN_NOT_INITIALIZED | The connector exists but has not been initialized |
CONN_LOAD_FAILED | The connector config file failed to parse or load |
CONN_CALL_FAILED | A method call on the connector failed |
CONN_DISABLED | The connector is disabled |
CONN_LIMIT_REACHED | License tier connector limit exceeded |
CONN_RELOAD_FAILED | Hot-reload of connectors failed |
Driver Errors
| Code | Description |
|---|---|
DRIVER_NOT_FOUND | The specified driver does not exist in the registry |
DRIVER_LOAD_FAILED | The driver DLL/assembly failed to load |
DRIVER_BLOCKED | The driver requires a license that is not active |
Script Errors
| Code | Description |
|---|---|
SCRIPT_NOT_FOUND | The script file was not found |
SCRIPT_ALREADY_RUNNING | A script with this name is already running |
SCRIPT_EXEC_FAILED | The script threw an error during V8 execution |
SCRIPT_LIMIT_REACHED | License tier concurrent script limit exceeded |
Agent Errors
| Code | Description |
|---|---|
AGENT_NOT_FOUND | The agent config or instance was not found |
AGENT_LIMIT_REACHED | Maximum concurrent agents limit exceeded |
AGENT_SAFETY_BLOCKED | The safety gate blocked the action (rate, workspace, speed, force) |
AGENT_DEVICE_MISSING | A required device for the agent is not available |
AGENT_AI_NOT_CONFIGURED | No AI provider configured for the agent |
AGENT_START_FAILED | The agent failed to start |
License Errors
| Code | Description |
|---|---|
LICENSE_LIMIT | A generic license tier limit was exceeded |
LICENSE_ACTIVATION_FAILED | License key activation failed |
LICENSE_EXPIRED | The license subscription has expired |
LICENSE_DEACTIVATION_FAILED | License deactivation failed |
Auth Errors
| Code | Description |
|---|---|
AUTH_RATE_LIMITED | Too many login attempts — try again later |
AUTH_INVALID_SESSION | The session token is invalid or expired |
AUTH_INVALID_PASSWORD | The provided password is incorrect |
Config Errors
| Code | Description |
|---|---|
CONFIG_READ_FAILED | Failed to read a configuration file |
CONFIG_WRITE_FAILED | Failed to write a configuration file |
CONFIG_INVALID | The configuration data is invalid |
AI Errors
| Code | Description |
|---|---|
AI_NOT_CONFIGURED | No AI API key or provider is configured |
AI_REQUEST_FAILED | The AI/LLM API request failed |
Internal Errors
| Code | Description |
|---|---|
INTERNAL_ERROR | An unexpected internal error occurred |
Connector Messages
connectors.list
List all loaded connector names.
// 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.
// 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).
// 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).
// 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.
// 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.
{ "type": "connectors.reload", "requestId": "6" }connector.call
Call a property (get/set) or action on a connector.
// 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
{ "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.
{ "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.
{ "type": "driver.template", "requestId": "10", "name": "TestDevice" }Stream Messages
stream.subscribe / stream.unsubscribe
{ "type": "stream.subscribe", "connector": "spectrometer", "stream": "spectrum" }
{ "type": "stream.unsubscribe", "connector": "spectrometer", "stream": "spectrum" }stream.data (server → client)
{
"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.
{ "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.
{ "type": "state.snapshot", "requestId": "20" }state.batch (server → client)
Pushed every 50ms when polled property values change (delta broadcasting).
{ "type": "state.batch", "updates": { "psu": { "voltage": 12.1, "power": 6.05 } } }Script Messages
scripts.list
{ "type": "scripts.list", "requestId": "12" }
// Response
{ "type": "scripts.list", "requestId": "12", "value": ["hello", "monitor"] }scripts.start
// 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.
{ "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
{ "type": "scripts.stop", "requestId": "14", "name": "my-script" }script.say (broadcast)
{ "type": "script.say", "script": "temp-monitor", "text": "Temperature is 25.3 degrees" }Agent Messages
agent.list
{ "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
{ "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
{ "type": "agent.stop", "agentId": "abc123" }
{ "type": "agent.pause", "agentId": "abc123" }
{ "type": "agent.resume", "agentId": "abc123" }agent.approve / agent.deny
{ "type": "agent.approve", "agentId": "abc123", "stepId": "step-id" }
{ "type": "agent.deny", "agentId": "abc123", "stepId": "step-id" }agent.state (broadcast)
{
"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
{ "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
| Type | Fields | Description |
|---|---|---|
ai.chat | sessionId, message, ttsEnabled? | Send user message, triggers agentic loop |
ai.tool_approve | requestId | Approve a pending tool call |
ai.tool_deny | requestId | Deny a pending tool call |
Server → Client (Streamed)
| Type | Fields | Description |
|---|---|---|
ai.delta | content, sessionId | Streaming text chunk |
ai.tool_call | tool, input, result | Tool executed |
ai.tool_pending | requestId, tool, input | Tool awaiting approval |
ai.image | tool, image, sessionId | Camera snapshot captured |
ai.chat | message, toolCalls | Final 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.
{ "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.
{ "type": "license.deactivate_driver", "driverId": "my-driver" }license.start_trial
{ "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.
{ "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:
{
"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.
{ "type": "update.available", "current": "0.1.0", "latest": "0.2.0", "downloadUrl": "..." }Example: JavaScript Client
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);
};