Fleet

Give a third party access to streams

Appwrite, a backend of your own, anything with an HTTPS client. It never touches MediaMTX and never holds a user's session: it asks this server for a short-lived playback URL and hands that to the app.

1

Create a key for them

Role viewer is enough to list cameras and mint playback URLs. Scope it to a site so they only see their cameras. Keys ↓

2

Hand over the camera ids

Their backend refers to a camera by its d_… id (or its path). Copy them from the directory, or let them call GET /v1/devices. Cameras ↓

3

They ask for a ticket, the app plays

POST /v1/playback → an HLS URL valid for minutes. Any HLS player: react-native-video, Safari, VLC, ExoPlayer. Reference ↓

API keys

One key per backend. viewer: list cameras, read their state, ask for playback tickets. operator: also add/remove cameras and send commands. admin: also manage keys and webhooks. A key with a site sees nothing outside it. The key is shown once, right here, at creation.

Camera directory

What an integrator sees from GET /v1/devices: the id their backend sends to /v1/playback, the path in the playback URL, the site a key can be scoped to, and whether MediaMTX is receiving the stream right now. Test plays the camera here exactly as their app would.

Try a playback request

Runs POST /v1/playback with the key saved top right — what their backend does for one user. The subject is free text that ends up in the audit trail (a user id, a session).

Recent tickets

Every playback ticket issued in the last 7 days: which camera, for whom, by which key, until when. A key that suddenly mints hundreds is the thing to look for.

Integrator reference

Base URL . Every call carries Authorization: Bearer fk_…. Errors are JSON {"error":"code"} with the HTTP status; 401 = bad key, 403 = role too low, 404 = no such camera (or outside the key's site), 429 = more than 120 tickets a minute from one key.

GET/v1/me What this key is: {id, role, site, name}. Use it to verify a key you were handed.
GET/v1/devices The cameras this key may see: {devices:[{id, name, path, site, kind, live, state, lastSeen, …}]}.
GET/v1/devices/{id} One camera, same shape.
POST/v1/playback Body {"device":"d_…"} or {"path":"hik1"}, optional ttlS (default 600, max 86400) and subject. Returns {url, expiresAt, ticket, device, path}. The URL is /live/<ticket>/<path>/index.m3u8; renew before expiresAt.
GET/live/…/index.m3u8 The HLS playlist and segments. No auth header: the ticket in the path is the authorisation, so the URL can go straight to a player. CORS *.
GET/v1/stream?key=… Server-sent events (stream.connected, stream.lost, fleet.device_offline, …) for live status without polling. Webhooks push the same events.

Samples, filled in for this server