> ## Documentation index
> Fetch the complete documentation index at: https://developer.rhombus.com/llms.txt
> Use it to discover all available pages before exploring further.

# Authenticate Media and Streams

> How to authenticate Rhombus snapshots, exact frames, clip downloads, live and recorded HLS, DASH and WebSocket streams, shared stream links and LAN streams, from servers, native apps and browsers.

Media (snapshots, frames, clips, live and recorded video, audio) comes from the media hosts, not the REST API, and the URLs the API returns **carry no credential**. You add one to every request. Three rules cover almost everything:

1. **From your server or a native app**, send your API key as headers (`x-auth-scheme: api-token` and `x-auth-apikey`), or an OAuth access token as `Authorization: Bearer <token>`.
2. **From a browser** (an `<img>`, a `<video>`, a JavaScript player or a WebSocket), send a [federated token](https://developer.rhombus.com/authentication/federated-tokens) as query parameters: `?x-auth-scheme=federated-token&x-auth-ft=<token>`. API keys and OAuth access tokens can't be sent in a URL.
3. **Cameras on your local network** accept federated tokens only. That's deliberate: on the local network, every request carries a short-lived token you can limit to one camera. See [Streaming over LAN](https://developer.rhombus.com/implementations/lan-streaming).

> **Note**: **Two different WebSockets.** Live video and audio stream over per-camera WebSocket URLs returned by `getMediaUris`; they follow the rules on this page. The **event** WebSocket at `ws.rhombussystems.com` delivers alerts and device changes; see [WebSocket authentication](https://developer.rhombus.com/websocket/authentication).

## Hosts

| What                              | US host                                                                             | Notes                                                                                             |
| --------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Snapshots, frames, clip downloads | `media.rhombussystems.com`                                                          | EU: `media.eu.rhombussystems.com`                                                                 |
| Live and recorded streams         | Per-camera hosts such as `<id>.dash.rhombussystems.com`, returned by `getMediaUris` | Some requests to `media.rhombussystems.com` redirect (307) to the camera's host; follow redirects |
| Cameras on your LAN               | `<camera>.lan.rhombussystems.com:8000`                                              | Public DNS for a private address; use the hostname, not the IP                                    |

Credentials are region-bound: a US key or token doesn't work on EU hosts.

## Snapshots and exact frames

The latest snapshot of a camera:

```text
https://media.rhombussystems.com/media/{cameraUuid}/{mediaRegion}/snapshot.jpeg
```

`cameraUuid` and `mediaRegion` come from [`getMinimalCameraStateList`](https://developer.rhombus.com/api-reference/camera-webservice/get-minimal-camera-state-list).

```bash cURL (server)
curl "https://media.rhombussystems.com/media/CAMERA_UUID/MEDIA_REGION/snapshot.jpeg" \
  -H "x-auth-scheme: api-token" \
  -H "x-auth-apikey: $RHOMBUS_API_KEY" \
  -o snapshot.jpeg
```

```bash cURL (OAuth)
curl "https://media.rhombussystems.com/media/CAMERA_UUID/MEDIA_REGION/snapshot.jpeg" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -o snapshot.jpeg
```

```html Browser
<img src="https://media.rhombussystems.com/media/CAMERA_UUID/MEDIA_REGION/snapshot.jpeg?x-auth-scheme=federated-token&x-auth-ft=TOKEN" />
```

For a frame at a specific time, call [`getExactFrameUri`](https://developer.rhombus.com/api-reference/video-webservice/get-exact-frame-uri) (timestamps in milliseconds). The URI it returns is **not signed**: fetch it with the same headers, or append the same query parameters in a browser.

## Saved clip downloads

Saved clips (from [`spliceV3`](https://developer.rhombus.com/api-reference/video-webservice/splice-v3) or the console) are downloaded from the media host by region and clip UUID. Take the region from the clip's `clipLocation`:

```bash
curl "https://media.rhombussystems.com/media/metadata/{clipLocation.region}/{clipUuid}.mp4" \
  -H "x-auth-scheme: api-token" \
  -H "x-auth-apikey: $RHOMBUS_API_KEY" \
  -o clip.mp4
```

Use `/media/play/{region}/{clipUuid}.mp4` instead to stream it inline (it supports Range requests). The credential's role needs permission to view saved video. A device-scoped federated token can fetch a saved clip only when every camera in the clip is the token's device.

## Live and recorded video

[`POST /api/camera/getMediaUris`](https://developer.rhombus.com/api-reference/camera-webservice/get-camera-media-uris) returns the stream URLs for a camera:

| Field                                           | Format                                | Use it with                                                                                   |
| ----------------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------- |
| `wanLiveM3u8Uri`                                | HLS                                   | Safari `<video>`, hls.js, AVPlayer, ExoPlayer, ffmpeg                                         |
| `wanLiveMpdUri`                                 | DASH                                  | dash.js                                                                                       |
| `wanLiveH264Uri`                                | H.264 over WebSocket (lowest latency) | A custom decoder, or the [React SDK](https://developer.rhombus.com/implementations/react-sdk) |
| `wanVodM3u8UriTemplate`, `wanVodMpdUriTemplate` | Recorded HLS / DASH                   | Replace `{START_TIME}` (Unix time, **seconds**) and `{DURATION}` (**seconds**)                |
| `lan…` fields                                   | The same formats on your LAN          | See [Streaming over LAN](https://developer.rhombus.com/implementations/lan-streaming)         |

Call `getMediaUris` with the credential your player will use, then authenticate each format as follows.

### HLS

Add the token to the playlist URL. On the cloud hosts, the playlist carries the token's query parameters to every segment URL, so players need nothing else:

```javascript
const url = `${wanLiveM3u8Uri}?x-auth-scheme=federated-token&x-auth-ft=${token}`;
if (video.canPlayType("application/vnd.apple.mpegurl")) {
  video.src = url; // Safari
} else {
  const hls = new Hls();
  hls.loadSource(url);
  hls.attachMedia(video);
}
```

From a native app or server, send the API key headers or `Authorization: Bearer` on every request instead.

### DASH

Add the token to the manifest URL. On the cloud hosts, live and recorded DASH manifests (and alert clip manifests) carry the token's query parameters to every segment URL, so one URL works in any player:

```javascript
const url = `${wanLiveMpdUri}?x-auth-scheme=federated-token&x-auth-ft=${encodeURIComponent(token)}`;
const player = dashjs.MediaPlayer().create();
player.initialize(video, url, true);
```

Only federated-token and shared-link query parameters are carried to segments. API keys and `x-auth-org` are never copied into segment URLs, so a server or native client sends its headers on every request.

For streams from a camera on your LAN, add the token to every request yourself. With dash.js 5, a request interceptor does it, and leaves alone any URL that already carries the token:

```javascript
const auth = `x-auth-scheme=federated-token&x-auth-ft=${encodeURIComponent(token)}`;
const withAuth = (url) => (url.includes("x-auth-ft=") ? url : `${url}${url.includes("?") ? "&" : "?"}${auth}`);

const player = dashjs.MediaPlayer().create();
player.addRequestInterceptor((request) => {
  request.url = withAuth(request.url);
  return Promise.resolve(request);
});
player.initialize(video, lanLiveMpdUris[0], true);
```

### WebSocket (H.264 and audio)

In a browser, put the token in the URL; from a server or native app, headers work too, as does an OAuth access token in `Authorization: Bearer`:

```javascript
const ws = new WebSocket(`${wanLiveH264Uri}?x-auth-scheme=federated-token&x-auth-ft=${token}`);
ws.binaryType = "arraybuffer";
```

A token minted with a `domain` works here: browsers send `Origin` on the WebSocket handshake. See [The domain field](https://developer.rhombus.com/authentication/federated-tokens#the-domain-field).

Open media sockets (live video, talk, robot and PTZ) close with code `1008` within 60 seconds when their credential is revoked or its access changes. With an OAuth token, talk audio is checked per message, and sending audio without the `physical` scope closes the socket with `1008` ([OAuth scopes](https://developer.rhombus.com/authentication/permissions#oauth-scopes)).

The [robot live socket](https://developer.rhombus.com/robots/live-socket) accepts API keys, OAuth access tokens and federated tokens, the same way.

## Partner keys

A partner calling a client organization's media from a server sends its partner credential as headers (`x-auth-scheme: api-token` with `x-auth-apikey`, or `Authorization: Bearer` with a partner OAuth token) plus `x-auth-org: <client org UUID>`. `x-auth-org` is a header only and is never copied into segment URLs. For a browser, mint a federated token for the client organization (send `x-auth-org` when you mint).

## Shared stream links

[`createSharedLiveVideoStream`](https://developer.rhombus.com/api-reference/camera-webservice/create-shared-live-video-stream) creates a link to one camera's live video that works without any Rhombus credential:

- `sharedLiveVideoStreamUrl` opens a Rhombus player page; embed it in an `<iframe>`.
- `sharedLiveM3U8StreamUrl` is a raw HLS playlist for your own player.

Anyone who has the link can watch, so treat it like a password:

- **`expirationTimeSecs` is the Unix time (in seconds) when the link stops working**, not a duration. Leave it out and the link never expires.
- Password-protected links ([`createProtectedSharedLiveVideoStream`](https://developer.rhombus.com/api-reference/camera-webservice/create-protected-shared-live-video-stream)): the player page asks for the password; for the raw HLS URL, send `Authorization: Basic <base64(":" + password)>` (an empty user name). A missing or wrong password returns 401 with `WWW-Authenticate: Basic`.
- Delete the share ([`deleteSharedLiveVideoStream`](https://developer.rhombus.com/api-reference/camera-webservice/delete-shared-live-video-stream)) to stop it immediately.

## Troubleshooting

| Symptom                                                   | Likely cause                                                                                                                                                                                                          | Fix                                                                                                                                                                       |
| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 403 `DOMAIN_NOT_ALLOWED` on a media URL                   | The token was minted with a `domain` that isn't the page's exact origin (scheme, host, port), or the request carries no `Origin` or `Referer` (a server, a native player, a page with `Referrer-Policy: no-referrer`) | Mint with your page's exact origin, or without `domain` for clients outside a browser                                                                                     |
| 401 on a media URL                                        | The credential is missing, damaged, expired or revoked; `errorCode` says which                                                                                                                                        | Send the credential on every request, or mint a fresh token ([Reading authentication errors](https://developer.rhombus.com/authentication#reading-authentication-errors)) |
| A LAN DASH manifest loads, segments fail                  | Segment requests have no token                                                                                                                                                                                        | Add it to every request (see [DASH](https://developer.rhombus.com/authentication/media#dash))                                                                             |
| Works on US hosts, fails on EU hosts                      | Keys and tokens are region-bound                                                                                                                                                                                      | Mint in the same region as the camera                                                                                                                                     |
| A device-scoped token fails on `api2`                     | `deviceUUid` tokens only work for media                                                                                                                                                                               | Use an unscoped token for REST calls                                                                                                                                      |
| 403 with a device-scoped token on a clip or a shared link | The clip includes other cameras, or the URL names no device                                                                                                                                                           | Mint an unscoped token for those requests                                                                                                                                 |
| 403 on a clip download                                    | The role can't view saved video, or the region in the URL is wrong                                                                                                                                                    | Check the role and `clipLocation`                                                                                                                                         |
| A media WebSocket closes with code `1008`                 | The credential was revoked or its access changed, or an OAuth token without `physical` sent talk audio                                                                                                                | Reconnect with a valid credential, or request the `physical` scope                                                                                                        |
| A shared link stopped working                             | `expirationTimeSecs` was a duration (an already-past Unix time)                                                                                                                                                       | Send a Unix time in seconds                                                                                                                                               |
