Live Stream WebSocket
This feature is not available in the Cloud API.
The live stream WebSocket is the Local API Gateway transport for high-rate robot data. It sends binary
MessageStream protobuf envelopes and is intended for video, point clouds, laser scans, poses, and
other live streams.
For live Custom KPI values from local-fleet member robots, use the separate
Fleet Custom KPI WebSocket. The Local Fleet KPI
route uses data-only JSON text frames, not an outer MessageStream envelope.
Flow
- Log in with
POST /apigateway/v2/auth/login. - List robots with
GET /apigateway/v2/robots. - List stream descriptors with
GET /apigateway/v2/robots/{robotId}/streams. - Select an
outputdescriptor. - Open the WebSocket with descriptor fields in the query string.
Endpoint
GET /apigateway/v2/robots/{robotId}/streams/connect
This route must be a WebSocket upgrade request.
Browser URL Example
ws://<agent-host>:19992/apigateway/v2/robots/{robotId}/streams/connect?access_token=<token>&componentName=camera&streamName=image&source=1&streamType=Nimbus.Messages.sensor_msgs.CompressedImage&maxRate=5&withoutDownSampling=false
Use wss:// only when the browser trusts the local HTTPS certificate. For HTTP development, an
http:// base URL produces a ws:// WebSocket URL and avoids browser certificate rejection.
Query Parameters
access_token: local API Gateway token. Browser clients usually strip the leadingbearerfrom the login response before placing the token in the URL.componentName: descriptorcomponentName; required for component streams.streamName: descriptorstreamName; required for all sources.source: descriptorstreamSourceType.streamType: descriptorstreamType; required for ROS streams and used by video source descriptors.url: descriptorsourceUrlfor ONVIF sources or a user-entered ONVIF/RTSP URL.endpoint: descriptorsourceEndpointfor device endpoint sources.usernameandpassword: optional ONVIF/RTSP credentials. Discovery never returns these values; ask for them only when connecting.maxRate: optional requested maximum rate. Omit it or use0to let agent defaults apply.withoutDownSampling: optional point-cloud downsampling control. Default isfalse.
Source-Specific Requirements
- Source
1, component stream:componentNameandstreamNameare required. - Source
2, ROS1 topic:streamNameandstreamTypeare required. - Source
3, ROS2 topic:streamNameandstreamTypeare required. - Source
4, ONVIF URL:urlis required.usernameandpasswordare optional. - Source
5, RTSP URL:urlis required and must start withrtsp://. Credentials can be supplied separately or embedded in the URL. - Source
6, device endpoint:endpointis required.
Browser Example
async function login(baseUrl, userToken) {
const response = await fetch(`${baseUrl}/apigateway/v2/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userToken })
});
if (!response.ok) throw new Error(`login failed: ${response.status}`);
return (await response.json()).token;
}
async function getJson(baseUrl, path, bearerToken) {
const response = await fetch(`${baseUrl}${path}`, {
headers: { Authorization: bearerToken }
});
if (!response.ok) throw new Error(`${path} failed: ${response.status}`);
return response.json();
}
async function connectFirstOutputStream(baseUrl, userToken) {
const bearerToken = await login(baseUrl, userToken);
const robots = await getJson(baseUrl, '/apigateway/v2/robots', bearerToken);
const robotId = robots.robotDataList[0].basicData.id;
const listed = await getJson(baseUrl, `/apigateway/v2/robots/${encodeURIComponent(robotId)}/streams`, bearerToken);
const stream = listed.robotStreams.find(item => item.direction === 'output');
if (!stream) throw new Error('no output stream descriptor found');
const query = new URLSearchParams();
query.set('access_token', bearerToken.replace(/^bearer\s+/i, ''));
query.set('componentName', stream.componentName || '');
query.set('streamName', stream.streamName || '');
query.set('source', String(stream.streamSourceType || 1));
query.set('streamType', stream.streamType || '');
if (stream.sourceUrl) query.set('url', stream.sourceUrl);
if (stream.sourceEndpoint) query.set('endpoint', stream.sourceEndpoint);
query.set('maxRate', '5');
query.set('withoutDownSampling', 'false');
const url = new URL(baseUrl);
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
const socket = new WebSocket(`${url.origin}/apigateway/v2/robots/${encodeURIComponent(robotId)}/streams/connect?${query}`);
socket.binaryType = 'arraybuffer';
socket.onmessage = event => {
console.log('binary MessageStream bytes', event.data.byteLength);
};
return socket;
}
Payload Format
Every WebSocket message is one serialized MessageStream protobuf envelope.
The envelope contains:
- stream identity fields;
dataType, which names theNimbus.Messages.*payload type;data, which contains the typed protobuf payload;- compression fields.
Decode in this order:
- Decode the WebSocket frame as a
MessageStreamenvelope. - If the envelope is compressed, inspect the compression type and inflate gzip payloads before payload decoding.
- Use
dataTypeto choose the typed protobuf parser fordata.
Common renderer targets include:
Nimbus.Messages.sensor_msgs.CompressedImageNimbus.Messages.sensor_msgs.ImageNimbus.Messages.sensor_msgs.LaserScanNimbus.Messages.geometry_msgs.PoseStampedNimbus.Messages.sensor_msgs.PointCloud2
Delivery Semantics
- This is live best-effort delivery.
- It is not a replay API.
- It does not guarantee delivery after reconnect.
- Each client has a bounded outbound queue with capacity
1; slow clients can drop old queued frames instead of growing memory. - Use
maxRatewhen a UI cannot keep up with the stream.
Common Errors
400: request is not a WebSocket upgrade or descriptor query fields are invalid.401: token is missing, expired, or invalid.404: local robot or stream target was not found.501: selected robot is a local-fleet member; WebSocket tunneling is not implemented.- Browser close code
1006: the browser did not receive a close frame. With local HTTPS, this commonly means the browser rejectedwss://because the local certificate is not trusted.