Skip to content

Streaming over LAN

Authenticate directly to Rhombus cameras and audio gateways on your local network for live video, recorded footage, and two-way audio.

On this page

Overview

When your client is on the same network as a Rhombus device, it can pull live video, recorded footage, and audio straight from the device instead of through the Rhombus cloud. You get lower latency and use no internet bandwidth.

Warning

Devices on your LAN accept only federated tokens. This is deliberate: a LAN request goes straight to the device on your local network, so the device takes a short-lived token, ideally limited to that one device, and never a long-lived API key or OAuth access token. Use your API key on your server to mint a short-lived federated token, then present the federated token to the device.

This guide covers:

  • How API tokens and federated tokens differ, and which one to use where
  • Minting a federated token and finding a device's LAN URIs
  • Authenticating LAN requests with headers or query parameters
  • Live H.264 and two-way audio over WebSocket, plus DASH/HLS for live and recorded video
  • Browser limitations and troubleshooting

API tokens vs federated tokens

These are two different credentials with different jobs.

API tokenFederated token
What it isYour integration's long-lived secret, created in the Rhombus ConsoleA short-lived token you mint with an API token via POST /api/org/generateFederatedSessionToken
LifetimeUntil you delete it, or until its expiry if it has oneThe durationSec you request (default 1 hour, at most 24 hours); it also stops working when you revoke it or the API key that minted it
PermissionsThe role assigned to the API tokenThe same role as the API token that minted it, optionally limited to one device
Headersx-auth-scheme: api-token and x-auth-apikeyx-auth-scheme: federated-token and x-auth-ft
Query parametersNot supported?x-auth-scheme=federated-token&x-auth-ft=TOKEN (the cloud API, cloud media and LAN devices)
Rhombus cloud APIYesYes, with headers or query parameters (not device-scoped tokens)
Rhombus cloud media (WAN stream URLs)Yes, with headers (server-side clients)Yes, with headers or query parameters
Devices on your LANNo, never acceptedYes, headers or query parameters. From a browser, only the WebSocket URLs with query parameters work (see Browser apps)
Where it may liveYour server onlyBrowsers, players, and LAN clients

A federated token is a bearer credential: anyone holding it can act with its role. For LAN streaming, mint device-scoped tokens with deviceUUid. A device-scoped token can only fetch that one device's media, and the cloud API rejects it. Also mint from an API token whose role is no broader than the viewer needs, and keep durationSec short.

Prerequisites

Implementation

  1. Mint a federated token on your server

    Call generateFederatedSessionToken with your API key. Your API key never leaves your server.

    import requests
    
    API_HEADERS = {
        "x-auth-scheme": "api-token",
        "x-auth-apikey": "YOUR_API_KEY",
        "Content-Type": "application/json",
    }
    
    response = requests.post(
        "https://api2.rhombussystems.com/api/org/generateFederatedSessionToken",
        headers=API_HEADERS,
        json={
            "durationSec": 3600,               # token lifetime in seconds
            "deviceUUid": "YOUR_CAMERA_UUID",  # recommended: only this device's media
        },
    )
    response.raise_for_status()
    body = response.json()
    federated_token = body["federatedSessionToken"]
    expires_at_ms = body["expiresAtMs"]  # Unix ms; mint the next token before then

    Example response:

    json
    {
      "federatedSessionToken": "Q2gP7m1kTnWb3sXyZ4aJ8w",
      "expiresAtMs": 1791370800000
    }
    FieldNotes
    durationSecOptional. The token's lifetime in seconds: 3600 (1 hour) if you leave it out, at most 86400 (24 hours; a larger value is reduced to that, and expiresAtMs shows the lifetime you got). Request only as long as the session needs.
    deviceUUidOptional, recommended for LAN. Limits the token to media (LAN and WAN) for one device; the token can't call the cloud API. Note the casing: deviceUUid.
    domainOptional. The browser origin allowed to use the token, as an absolute origin such as https://app.example.com. Requests to Rhombus cloud hosts from any other origin get 403 DOMAIN_NOT_ALLOWED. To limit a LAN token, use deviceUUid and a short durationSec.

    The response's expiresAtMs is when the token expires, in Unix milliseconds. Mint the next token before then.

    The token can never do more than the API token that minted it. If that role only allows live video, the device serves live streams and refuses recorded footage.

  2. Get the device's LAN URIs

    On your server, call POST /api/camera/getMediaUris with cameraUuid, or /api/audiogateway/getMediaUris with gatewayUuid, using your API key. A device-scoped federated token can't call the cloud API.

    Python
    response = requests.post(
        "https://api2.rhombussystems.com/api/camera/getMediaUris",
        headers=API_HEADERS,
        json={"cameraUuid": "YOUR_CAMERA_UUID"},
    )
    response.raise_for_status()
    uris = response.json()
    FieldWhat it isExample
    lanLiveH264UrisLive H.264 over WebSocket (lowest latency)wss://192-168-1-50.lan.rhombussystems.com:8000/DEVICE_UUID/ws
    lanLiveOpusUrisLive two-way audio over WebSocketwss://192-168-1-50.lan.rhombussystems.com:8000/DEVICE_UUID/audio
    lanLiveMpdUris / lanLiveM3u8UrisLive DASH / HLShttps://192-168-1-50.lan.rhombussystems.com:8000/DEVICE_UUID/live/live.mpd
    lanVodMpdUrisTemplates / lanVodM3u8UrisTemplatesRecorded footage from the device's own storagehttps://192-168-1-50.lan.rhombussystems.com:8000/DEVICE_UUID/store/{START_TIME}_{DURATION}/clip.mpd
    lanCheckUrlsReachability checkhttps://192-168-1-50.lan.rhombussystems.com:8000/DEVICE_UUID/
    • Each field is an array because a device can have more than one LAN address. Use the first one your client can reach.
    • In the VOD templates, replace {START_TIME} with a Unix timestamp in seconds and {DURATION} with a length in seconds.
    • The LAN hostname is public DNS that resolves to the device's private IP, and the device serves a publicly trusted certificate for it. Connect using the hostname exactly as returned, not the raw IP, and keep normal TLS verification on. Devices in the EU region use .lan.eu.rhombussystems.com hostnames (API Regions).
  3. Present the federated token to the device

    Send the federated token on every request to the device, including each DASH/HLS segment and the WebSocket upgrade. Use whichever form your client supports:

    For native apps, servers, curl, and ffmpeg:

    x-auth-scheme: federated-token
    x-auth-ft: YOUR_FEDERATED_TOKEN

    The device checks the token with Rhombus: it must be unexpired, belong to the device's organization, and carry a role that can view this device. The device caches a successful check for up to 60 seconds, so a token can keep working for up to a minute after it expires or is revoked. When a viewer's session ends, revoke its token with POST /api/org/revokeFederatedSessionToken (body {"federatedSessionToken": "..."}), and keep durationSec short.

Code examples

Live DASH manifest

cURLbash
TOKEN="YOUR_FEDERATED_TOKEN"

curl -H "x-auth-scheme: federated-token" -H "x-auth-ft: $TOKEN" \
  "https://192-168-1-50.lan.rhombussystems.com:8000/YOUR_CAMERA_UUID/live/live.mpd"

A valid token returns the DASH manifest (200). A missing, expired, or unauthorized token returns 401.

Recorded footage with ffmpeg

ffmpeg passes -headers on to every playlist and segment request. This saves 60 seconds of footage starting at 1767225600 (2026-01-01 00:00:00 UTC):

ffmpegbash
ffmpeg -headers $'x-auth-scheme: federated-token\r\nx-auth-ft: '"$TOKEN"$'\r\n' \
  -i "https://192-168-1-50.lan.rhombussystems.com:8000/YOUR_CAMERA_UUID/store/1767225600_60/clip.m3u8" \
  -c copy clip.mp4

Live H.264 over WebSocket

import asyncio
import json
from urllib.parse import urlencode

import requests
import websockets  # pip install websockets

API_HEADERS = {
    "x-auth-scheme": "api-token",
    "x-auth-apikey": "YOUR_API_KEY",
    "Content-Type": "application/json",
}
CAMERA_UUID = "YOUR_CAMERA_UUID"


def mint_device_token(device_uuid: str) -> tuple[str, int]:
    response = requests.post(
        "https://api2.rhombussystems.com/api/org/generateFederatedSessionToken",
        headers=API_HEADERS,
        json={"durationSec": 3600, "deviceUUid": device_uuid},
    )
    response.raise_for_status()
    body = response.json()
    # Mint the next token before expiresAtMs (Unix milliseconds)
    return body["federatedSessionToken"], body["expiresAtMs"]


def lan_h264_uri(camera_uuid: str) -> str:
    response = requests.post(
        "https://api2.rhombussystems.com/api/camera/getMediaUris",
        headers=API_HEADERS,
        json={"cameraUuid": camera_uuid},
    )
    response.raise_for_status()
    uris = response.json().get("lanLiveH264Uris") or []
    if not uris:
        raise SystemExit("The camera reported no LAN address")
    return uris[0]


async def watch(uri: str, token: str, max_frames: int = 30) -> None:
    auth = urlencode({"x-auth-scheme": "federated-token", "x-auth-ft": token})
    async with websockets.connect(f"{uri}?{auth}") as ws:
        frames = 0
        async for message in ws:
            if isinstance(message, str):  # one text init message comes first
                print("init:", json.loads(message))
                continue
            timestamp_ms, offset = None, 0
            while offset + 4 <= len(message):
                tlv_type = message[offset]
                length = int.from_bytes(message[offset + 1 : offset + 4], "big")
                value = message[offset + 4 : offset + 4 + length]
                offset += 4 + length
                if tlv_type == 0x02:
                    timestamp_ms = int.from_bytes(value, "big")
                elif tlv_type in (0x00, 0x01):  # frame data is always last
                    kind = "keyframe" if tlv_type == 0x00 else "delta"
                    print(f"{kind}: {len(value)} bytes at {timestamp_ms}")
                    frames += 1
                    break
            if frames >= max_frames:
                break


token, expires_at_ms = mint_device_token(CAMERA_UUID)
asyncio.run(watch(lan_h264_uri(CAMERA_UUID), token))

Example output from the Python script:

init: {'action': 'init', 'width': 1920, 'height': 1080, 'codec': 'h264', 'framerate': 15}
keyframe: 48213 bytes at 1791158465584
delta: 3120 bytes at 1791158465651

Each binary message is a sequence of TLV records (1-byte type, 3-byte big-endian length, value) ending with the H.264 frame data. The full type table, including embedded AI detections, is in LAN Realtime Detection Overlay. For a ready-made browser player, use RhombusRealtimePlayer with connectionMode="lan" from the React SDK.

Audio over WebSocket

The lanLiveOpusUris socket carries audio in both directions and uses the same federated token authentication.

Listening

Each binary message from the device holds three TLV records. Each record header is 1 bit (echo flag), 7 bits (type), and a 24-bit big-endian length, followed by the value.

TypeValue
0Sample count (2 bytes, big-endian)
1Unix timestamp in milliseconds (8 bytes, big-endian)
4One Opus packet, 48 kHz mono. The echo flag is set if the device was playing audio when this was captured.

Talking

To play audio on the device's speaker, send binary messages on the same socket. Each record is a 4-byte ASCII type, a 4-byte big-endian length, then the payload. Each audio record carries exactly 20 ms of audio.

TypePayload
opusOne Opus packet, 48 kHz mono
pcm (trailing space)960 samples of 48 kHz mono signed 16-bit little-endian PCM (length 1920)
ctrl8-byte big-endian Unix timestamp in milliseconds to start playback. Send 0 to play every packet even if delayed, or -1 to drop late audio and stay real time.

By default the device plays every Opus packet, even late ones, and drops late PCM to stay real time.

Browser apps

  • WebSocket streams (lanLiveH264Uris, lanLiveOpusUris) work from any origin with the token in the query string.
  • LAN DASH and HLS (lanLive* and lanVod* URLs) can't be loaded by browser JavaScript such as dash.js or hls.js on your own domain, because devices only allow Rhombus origins for cross-origin requests. Play LAN DASH/HLS from a native app or server, or use the WebSocket stream in the browser.
  • Never put an API key in browser code. Mint federated tokens on your server and hand only those to the browser.

Devices on older firmware

Devices running firmware released before mid-April 2026 only read the token from a cookie. If a native client must support them, also send:

Cookie: RSESSIONID=RFT:YOUR_FEDERATED_TOKEN

Current firmware accepts this cookie too, so a native client can send it alongside the headers.

Troubleshooting

401 on every request

No token reached the device, x-auth-scheme isn't exactly federated-token, or you sent an API key instead of a federated token. Devices never accept API keys.

401 after working for a while

The federated token expired, or it was revoked (directly, or because the API key that minted it was deleted). Mint a new one before the expiresAtMs in the mint response.

401 for one device, fine for others

The token was minted with deviceUUid for a different device, or its role can't view this device.

Live works, recorded footage is refused

The token's role, inherited from the API key that minted it, only allows live video.

Connection closes with no response

The device couldn't reach the Rhombus cloud to check the token.

Works in curl, fails in a browser

Browsers block cross-origin DASH/HLS requests to the device. See Browser apps.

TLS or hostname errors

Use the LAN hostname from getMediaUris, not the raw IP address.

Next steps