Skip to main content

Executor availability WebSocket

Use this public WebSocket to maintain a current list of executors that are rentable right now. It replaces repeated polling of GET /executors for availability changes.

Connect to:

wss://lium.io/api/executors/ws

The endpoint requires no API key. The server sends JSON text frames only; clients do not send messages.

Send a User-Agent header​

The CDN in front of lium.io rejects a request that has no User-Agent header. It answers 403 Forbidden before the request gets to the API. Set any non-empty User-Agent, and the handshake completes with 101 Switching Protocols.

To identify a 403 from the CDN, look at the Server header of the response. The CDN sends Server: CloudFront. The API always sends Server: uvicorn.

Browsers, curl, Python websockets and Go gorilla/websocket set the header for you. These clients do not set it, so you must add it:

ClientAdd the header
websocatwebsocat -t -H='User-Agent: my-app/1.0' -B 2000000 wss://lium.io/api/executors/ws
Node wsnew WebSocket(url, { headers: { 'User-Agent': 'my-app/1.0' } })
Rust tungsteniteGive connect a request from http::Request::builder().header("User-Agent", "my-app/1.0")

websocat also needs the -B option, because the snapshot frame is larger than its default 64 KB buffer.

Keep a local executor map​

The first frame on every connection is always a complete snapshot. Replace local state with it. Then apply each upsert or delete by executor_id.

const executors = new Map<string, ExecutorAvailability>()
const socket = new WebSocket('wss://lium.io/api/executors/ws')

socket.onmessage = ({ data }) => {
const frame = JSON.parse(data)

switch (frame.type) {
case 'snapshot':
executors.clear()
for (const executor of frame.executors) executors.set(executor.id, executor)
break
case 'upsert':
executors.set(frame.executor_id, frame.executor)
break
case 'delete':
executors.delete(frame.executor_id)
break
case 'heartbeat':
break
}
}

Frames​

Every frame has these envelope fields:

FieldMeaning
typesnapshot, upsert, delete, or heartbeat
schema_versionProtocol version. The current version is 1.
seqMonotonic sequence number within this WebSocket connection.
tsServer publish time as Unix seconds.

Snapshot​

{
"type": "snapshot",
"schema_version": 1,
"seq": 42,
"ts": 1787572800.5,
"executors": [
{
"id": "ad83de56-8a04-4ab1-ac5a-4ab007b4e2b3",
"machine_name": "NVIDIA H200",
"price_per_gpu": 2.5,
"gpu_count": 8,
"available_gpu_count": 8,
"uptime_in_minutes": 4815,
"specs": { "gpu": { "count": 8, "details": [] } }
}
]
}

Upsert and delete​

An upsert adds an executor to the rentable set or replaces its full availability projection. Its executor object is the same projection the snapshot carries; the examples on this page show a few of its fields, not all of them. A delete means it is no longer rentable; the server does not expose the reason.

{
"type": "upsert",
"schema_version": 1,
"seq": 43,
"ts": 1787572801.0,
"executor_id": "ad83de56-8a04-4ab1-ac5a-4ab007b4e2b3",
"executor": { "id": "ad83de56-8a04-4ab1-ac5a-4ab007b4e2b3", "available_gpu_count": 4 }
}
{
"type": "delete",
"schema_version": 1,
"seq": 44,
"ts": 1787572802.0,
"executor_id": "ad83de56-8a04-4ab1-ac5a-4ab007b4e2b3"
}

heartbeat has only the envelope and confirms that the connection is still live.

Reconnects and compatibility​

There is no resume token. On every reconnect, discard any partial state when the next snapshot arrives and start applying subsequent frames from that snapshot. A later snapshot on an existing connection also replaces the full local state.

The server closes with WebSocket code 1013 when it is not ready, the connection limit is reached, or a slow client has fallen behind. Retry with exponential backoff.

Treat schema_version as a contract: reconnecting cannot make a client compatible with a higher version. Add support for the new version before accepting it.

The executor object is an availability projection. It intentionally excludes internal details and most high-churn telemetry; only use fields present in a received frame. uptime_in_minutes is included for marketplace filtering and sorting, but its minute-by-minute change does not trigger an upsert by itself.