Monitor the program output (WHEP)
A native or desktop client can play the studio's program output — the exact composited feed the cloud encoder is sending to your destinations — over WebRTC WHEP. This is the app analogue of the program-preview tile in the web studio: a low-latency confidence monitor of what is actually going out.
WHIP / WHEP documents the WebRTC play protocol, but a player still needs a read token and the URL to play. This guide is the one endpoint that hands you both, scoped to your studio's program path.
The same credential is on
/v1now.POST /v1/studios/{id}/monitormints this token through the public gateway, authenticated with ansk_live_key or an OIDC bearer, and adds an SRT alternative for clients that cannot speak WHEP. See Go live from a phone over/v1. Everything below about WHEP, the token and the expiry applies there unchanged.
What you talk to
The monitor descriptor comes from the studio control plane (the studio API),
the same host you already use for login and contribution — not the /v1 REST
gateway. Playback itself is a direct WebRTC dial to the media host in the
descriptor.
| Development | Production | |
|---|---|---|
| Studio API (control plane) | https://studio-api-dev.traxstreaming.live |
https://studio-api.traxstreaming.live |
| Identity (OAuth login) | https://auth-dev.traxstreaming.live |
https://auth.traxstreaming.live |
| WHEP playback | the whepUrl in the descriptor |
the whepUrl in the descriptor |
Always dial the exact whepUrl the descriptor hands you rather than
reconstructing the media host yourself.
Authenticate as the user
The mint call carries the user's access token: Authorization: Bearer <jwt>,
the OAuth 2.0 + PKCE token described in Build a client.
Any studio member may monitor — owner or collaborator, in any role
(view is enough). The mint verifies your membership before issuing the read
capability.
1. Fetch the monitor descriptor
Ask the studio for a program-monitor capability. No request body is required:
POST /api/v1/studios/{studioId}/monitor
Authorization: Bearer <jwt>
curl -sS -X POST \
https://studio-api-dev.traxstreaming.live/api/v1/studios/$STUDIO_ID/monitor \
-H "Authorization: Bearer $USER_JWT"
The 200 response is the descriptor you play with:
{
"whepUrl": "https://<media-host>:8889/s/<studioId>/pp/whep",
"streamName": "s/<studioId>/pp",
"token": "<JWT — aud trax-mediamtx-read>",
"expiresAt": "2026-08-11T20:15:00Z"
}
| Field | What it is |
|---|---|
whepUrl |
The WHEP play URL to POST your SDP offer to. Use it verbatim. |
streamName |
The program path this monitor reads (s/<studioId>/pp). |
token |
The read token — a short-lived JWT (aud: trax-mediamtx-read) scoped to exactly that one path. This is the Bearer for the WHEP POST, not your user token. |
expiresAt |
RFC 3339 expiry of the read token. |
403 means you are not a member of the studio; 503 means the monitor signer
or media endpoint is momentarily unavailable — retry.
2. Play it over WHEP
This is the standard WHEP play flow from WHIP / WHEP,
with one rule: the Bearer on the signaling POST is the descriptor's read
token, not the user token.
- Create a recv-only
RTCPeerConnection. - Create an SDP offer, gather ICE (non-trickle — wait for
iceGatheringState === "complete"). POSTthe offer towhepUrlwithContent-Type: application/sdpandAuthorization: Bearer <token>.- Set the returned SDP answer as the remote description and render the track.
// descriptor = the JSON from POST /monitor
const pc = new RTCPeerConnection();
pc.addTransceiver('video', { direction: 'recvonly' });
pc.addTransceiver('audio', { direction: 'recvonly' });
pc.ontrack = (e) => { videoEl.srcObject = e.streams[0]; };
await pc.setLocalDescription(await pc.createOffer());
// non-trickle: let ICE finish before signaling
await new Promise((resolve) => {
if (pc.iceGatheringState === 'complete') return resolve();
pc.addEventListener('icegatheringstatechange', () => {
if (pc.iceGatheringState === 'complete') resolve();
});
});
const res = await fetch(descriptor.whepUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/sdp',
Authorization: `Bearer ${descriptor.token}`, // the READ token
},
body: pc.localDescription.sdp,
});
await pc.setRemoteDescription({ type: 'answer', sdp: await res.text() });
Audio arrives as Opus (feeds contributed as AAC are transcoded on the way out) — see WHIP / WHEP for the codec and failure-mode tables.
Token semantics — re-provision per open
- Short-lived. The read token lasts about 5 minutes (hard-capped at 15). Do not cache or share it — it travels in the WHEP request.
- mediamtx authorizes a WHEP read only at connect time. An established
monitor keeps playing after the token expires; a fresh token is only needed to
(re)connect. So the rule is simple: call
POST /monitoragain every time you open (or reopen) the player, and don't bother refreshing a token for a connection that is already up. - Single-path scope. The token authorizes exactly
s/<studioId>/ppand nothing else — it cannot read any other source or studio.
The program only flows when the studio is live
The endpoint always mints the capability, but media flows only while the studio is live and pushing its program-preview feed. A monitor opened while the studio is off-air connects successfully but shows no video yet — it should start rendering frames when the studio goes live.
Keep the WHEP peer connection up across go-live (or reconnect on a short retry) so the monitor lights up the moment the program starts. Treat "connected, no frames" as a normal pre-live state, not an error.
Where to go next
- WHIP / WHEP — the WebRTC publish/play protocol, token audiences, and failure modes.
- Drive the studio canvas — control the layout your monitor is showing over the studio WebSocket.
- Build a client — the transport map and native OAuth
- PKCE login that issues the user token.
- Contribute a camera from a phone (SRT) — the publish side of the same studio.
Knowing when to connect (and when to stop)
Do not poll stream-status to find out whether the program is worth watching.
GET /v1/studios/{id}/events is a Server-Sent Events stream that tells you when
the studio goes on air, when it goes off, and when any source's publishing
flips — one connection instead of a request every few seconds, which matters on
the same cellular uplink your device may be publishing over.
Open it once, wait for event: streamStatus with "live": true, then mint your
monitor token and connect WHEP. When "live" goes false, tear the peer
connection down.
The full event vocabulary, the Last-Event-ID reconnect contract and the limits
are in Go live from a phone.