Skip to main content

API and protocols

All REST paths below are relative to the API origin. Platform routes use a normal platform Bearer JWT. Device routes use a runner-audience JWT unless the row explicitly states dual authentication.

REST surface

Expert and platform routes

Method and pathAuthorizationContract
GET /v1/remote-desktop/machines/mineExpert with org access, or SuperadminEntitled assigned machines; includes protected VNC credentials for the caller's usable machine
GET /v1/remote-desktop/sessions?orgId=<uuid>Same org authorizationLatest 50 sessions, newest first
POST /v1/remote-desktop/connectSame org authorizationBody { "orgId": "<uuid>" }; returns the server transport plan
POST /v1/remote-desktop/sessions/:id/endSession-owner platform JWT or runner JWT for the same orgIdempotently ends a direct session
POST /v1/remote-desktop/webrtc/telemetryValid platform JWT; 120 requests/minuteBest-effort browser telemetry; returns 204

machines/mine exposes placement and display information, not device network coordinates. Its machine object may include vncUsername and vncCredential because noVNC must authenticate to macOS Screen Sharing. Those values are not returned by the operations fleet endpoint.

Device routes

Method and pathAuthorizationContract
GET /v1/remote-desktop/webrtc/ice-serversMachine-bound runner JWTSTUN plus short-lived TURN configuration for an active assigned machine
POST /v1/remote-desktop/validate-tokenRunner guardConsumes { token, machineId } once and returns { ok: true, sessionId }
POST /v1/remote-desktop/machines/:id/tunnel-healthRunner guard for the same orgBody `{ "healthy": true
POST /v1/remote-desktop/sessions/:id/endRunner JWT for the session orgBody may include closeReason

Operations routes

All operations routes enforce MFA and platform roles.

Method and pathRolesPurpose
GET /admin/machinesSuperadmin, Account ManagerFleet overview; AM network details are redacted
GET /admin/machines/suggestions?orgId=<uuid>SuperadminJurisdiction-ranked pooled inventory
POST /admin/machinesSuperadminCreate an unassigned pooled machine
PATCH /admin/machines/:idSuperadminEdit placement, labels, connection config or metadata
POST /admin/machines/:id/assignSuperadminBody { orgId, reason? }
POST /admin/machines/:id/unassignSuperadminReturn to pool; optional reason
POST /admin/machines/:id/maintenanceSuperadminEnter maintenance; optional reason
POST /admin/machines/:id/retireSuperadminRetire; optional reason
POST /admin/machines/:id/restoreSuperadminRestore; optional reason
GET /admin/machines/:id/assignmentsSuperadminAppend-only assignment history
POST /admin/machines/:orgId/computer-accessSuperadminBody `{ "enabled": true
POST /admin/machines/:id/connector-tokenSuperadminMint a machine-bound connector JWT; 503 if RUNNER_JWT_SECRET is absent

Create accepts tailscaleHostname and optional vncPort, vmName, placement, labels, connectMode, publicHostname, and metadata. Update accepts the same mutable fields except tailscaleHostname. Ports are restricted to 1-65535; connect mode is one of proxy, direct_wss, or webrtc.

Connect response

The response is a discriminated union on mode.

WebRTC plan

{
"mode": "webrtc",
"signalingUrl": "wss://api.example/v1/remote-desktop/signal?...",
"iceServers": [{ "urls": ["stun:..."] }],
"iceServersExpiresAt": null,
"session": { "orgId": "...", "machineId": "..." },
"connector": { "machineId": "...", "role": "browser" },
"vncUsername": "...",
"vncCredential": "...",
"fallback": {
"mode": "p2p_rfb",
"signalingUrl": "wss://api.example/v1/remote-desktop/signal?...",
"iceServers": [],
"iceServersExpiresAt": null,
"fallback": {
"mode": "proxy",
"wsUrl": "wss://api.example/v1/remote-desktop?token=...&orgId=...",
"iceServers": [],
"iceServersExpiresAt": null
}
}
}

Credential values are nullable. The browser first attempts native video, then uses the P2P descriptor for RFB, then its nested relay plan.

Direct WSS plan

{
"mode": "direct",
"sessionId": "...",
"wssUrl": "wss://machine.example/vnc?token=<single-use>",
"expiresAt": "...",
"vncUsername": "...",
"vncCredential": "...",
"iceServers": [],
"iceServersExpiresAt": null,
"fallback": {
"mode": "proxy",
"wsUrl": "wss://api.example/v1/remote-desktop?token=...&orgId=..."
}
}

The default token lifetime is 60 seconds and is capped at one hour. The direct plan is issued only when the machine tunnel is healthy and Redis can store the single-use token.

Proxy plan

{
"mode": "proxy",
"wsUrl": "wss://api.example/v1/remote-desktop?token=...&orgId=...",
"vncUsername": "...",
"vncCredential": "...",
"iceServers": [],
"iceServersExpiresAt": null
}

The URL contains a live platform JWT. Do not log or persist the response.

WebSocket endpoints

Raw relay: /v1/remote-desktop

This exact path is a raw RFC 6455 WebSocket, not Socket.IO. The platform JWT is accepted in the token query parameter or as a JWT-like WebSocket subprotocol; orgId selects the authorized organization. The negotiated data subprotocol may be binary or base64, although the production noVNC path uses binary RFB bytes.

Authentication failure closes with application code 4001; authorization failure closes with 4003. An upstream TCP failure closes with 1011.

Signaling: /v1/remote-desktop/signal

Browser URL parameters are role=browser, platform token, and orgId. Connector parameters are role=agent and the machine-bound runner token. The browser is the offerer and the connector is the answerer.

After browser authentication, the server creates an audit row and sends:

{ "type": "ready", "sessionId": "<server-assigned>" }

Subsequent accepted frames are at most 64 KiB and use one of:

{ "type": "offer", "sessionId": "...", "sdpType": "offer", "sdp": "..." }
{ "type": "answer", "sessionId": "...", "sdpType": "answer", "sdp": "..." }
{ "type": "ice", "sessionId": "...", "cand": "...", "mid": "..." }
{ "type": "bye", "sessionId": "..." }

Malformed JSON, unknown types and oversized frames are dropped. The server replaces every browser-supplied sessionId with its assigned value, so one browser cannot address another session. Connector-to-browser replies are constrained by the connector's authenticated machine binding.

Redis pub/sub carries cross-pod signaling on rdwebrtc:machine:<machineId> and rdwebrtc:session:<sessionId>. With no Redis, delivery is in-process and correct only when browser and connector land on the same single API pod.

WebRTC media and DataChannels

The negotiated video codec is H.264 with packetization mode 1. The browser prefers H.264 and validates the reported negotiated codec when the browser exposes it. The connector publishes a send-only track; the browser receives only. ICE configuration comes from the connect plan and the connector's authenticated ICE endpoint.

The connector accepts only the allowlisted channel labels below. Only the first input channel is accepted; duplicates are closed.

LabelDirectionPayload
inputBrowser to connectorReliable ordered UTF-8 JSON input and quality control; maximum input event frame 512 bytes
rfbBidirectionalRaw RFB byte stream bridged to guest VNC; fallback path

Input protocol v1

Coordinates are normalized to encoded video content, not the CSS video box. The connector maps them to the current display's Quartz bounds and physical pixels, including Retina displays.

EventShapeLimits
Move/drag{"v":1,"t":"pm","x":0.5,"y":0.25,"m":0}x,y in [0,1]; optional DOM buttons mask m in 0..7
Pointer down/up{"v":1,"t":"pd","x":0.5,"y":0.25,"b":0,"n":1}t is pd or pu; button 0..2; optional click count 1..3
Scroll{"v":1,"t":"sc","x":0.5,"y":0.25,"dx":0,"dy":24}Each axis -120..120
Key down/up{"v":1,"t":"kd","c":"KeyA"}t is kd or ku; explicit KeyboardEvent.code allowlist

Compact events may include modifier mask f in 0..15 for Shift, Control, Alt and Command. The connector also accepts the browser's strict verbose physical-video-pixel envelope and normalizes it into the same internal model. It is not a generic JSON input path.

Valid events are limited to 240 per second with a burst of 120. Unknown fields, trailing JSON, invalid values, unknown event types and binary input are dropped without consuming rate capacity. The native boundary checks Accessibility for every event and fails closed.

The input gate opens only after an authenticated offer is accepted. A transient disconnect closes the gate and releases held keys/buttons; it may reopen only for the same accepted session. Terminal teardown closes it permanently before the peer or channel is destroyed.

Session lifecycle and close semantics

  • Relay sessions start active when the API relay opens.
  • WebRTC sessions start active when the browser signaling socket is accepted.
  • Direct WSS sessions start pending, become active when the device consumes the token, and become ended on close.
  • Browser and device end calls are idempotent. The first update sets ended_at, close_reason, and status='ended'.
  • Browser signaling close records client_closed; connector close records device_closed. Relay failures use bounded reasons such as upstream_error, upstream_closed, client_closed, or client_error.

Telemetry payload

The browser posts kind: periodic|disconnect with optional reason, rttMs, jitterMs, freezeCount, sessionSeconds, candidateType, and relayProtocol. Numeric values are clamped and strings are mapped to bounded labels before entering Prometheus. Telemetry is advisory, fire-and-forget, and must never delay or fail a desktop session.