Control the mixer from your app
Someone is on the show from a phone, and they want the two controls every contributor actually wants: am I muted, and how loud am I. This guide is the API behind that — read a studio's audio mix, then move one fader or one mute.
Which host. These endpoints are on dev only for now:
https://api-dev.traxstreaming.live/v1with a dev key. This page will say so when they reach production.
Three calls, and the read is the one you start with:
GET /v1/studios/{id}/audioreturns the whole mix — the master strip plus one strip per source.PATCH /v1/studios/{id}/audio/sources/{sourceId}moves one channel.PATCH /v1/studios/{id}/audio/mastermoves the master.
The thing to internalise: you are reaching the same mixer the operator is looking at. Not a copy of it, not a phone-side shadow of it. Mute a mic here and the fader moves on the operator's screen, the encoder applies it, and the viewers stop hearing that person — all from the one write.
Before you start
- An API key holding
audio:readto see the mix andaudio:writeto change it.audio:writeincludes read, so a key that controls the mixer does not need both. Neither is in the default scope set: a studio's mix tells you who is muted and how loud each person is, which is a picture of the show rather than inventory, so you ask for it by name. Create the key under Developer → API keys in your account. - The studio id, and the source id of whoever you are controlling. Both
come from
GET /v1/studios/{id}/sources.
Step 1 — read the mix
curl -s https://api-dev.traxstreaming.live/v1/studios/$STUDIO/audio \
-H "Authorization: Bearer $TRAX_API_KEY"
{
"master": { "muted": false, "volume": 1 },
"sources": [
{
"sourceId": "0520baa7-…",
"name": "Microphone",
"type": "webrtc-audio-input",
"active": true,
"muted": false,
"volume": 0.8,
"pan": 0,
"soloed": false,
"trim": 1,
"muteGroups": []
},
{
"sourceId": "4fd7c67d-…",
"name": "Camera A",
"type": "generic-input",
"active": true,
"muted": false,
"volume": 1,
"pan": 0,
"soloed": false,
"trim": 1,
"muteGroups": []
}
]
}
One call returns every strip, on purpose. A fader surface draws all of its channels at once, and fetching them one at a time would let the strips disagree with each other halfway through a render.
Every source gets a strip, including ones with no audio track. The strip is
per-source state that persists and is re-applied to the encoder whenever that
source is composited — so a camera has one even if nothing is coming out of it.
Read type and active and draw what makes sense for your app.
Untouched sources report volume: 1, not volume: 0. A source nobody has
moved in the mixer is running at unity, unmuted, centre. Those are the values
the encoder is applying to it right now, so reporting them is a measurement,
not a default standing in for missing data.
Step 2 — mute someone
curl -s -X PATCH \
https://api-dev.traxstreaming.live/v1/studios/$STUDIO/audio/sources/$SOURCE \
-H "Authorization: Bearer $TRAX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "muted": true }'
{
"sourceId": "0520baa7-…",
"name": "Microphone",
"type": "webrtc-audio-input",
"active": true,
"muted": true,
"volume": 0.8,
"pan": 0,
"soloed": false,
"trim": 1,
"muteGroups": []
}
Note what did not happen: volume is still 0.8. Send only the fields you
are changing. Anything you leave out keeps the value it had, which is what
lets a phone toggle mute without knowing — or destroying — the mix an operator
spent the pre-show building.
Set muted. Do not write volume: 0 and call it mute. They are different
things and the difference is the whole point: mute is an independent gate, and
the fader keeps its position underneath it, so unmuting returns to exactly the
level the operator was at. Muting by zeroing the fader throws that position
away, and the operator has to find it again on air.
The response is the whole strip, not just what you sent, so you can render from it without a follow-up read.
Step 3 — set your level
curl -s -X PATCH \
https://api-dev.traxstreaming.live/v1/studios/$STUDIO/audio/sources/$SOURCE \
-H "Authorization: Bearer $TRAX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "volume": 0.6 }'
volume is a linear gain, not decibels. 0.0 is silence, 1.0 is unity
(0 dB), and above 1.0 amplifies, up to a ceiling of 10.0. If you send -6
expecting six decibels down, you get a 400 — deliberately. The alternative is
clamping it to 0.0, which would silence someone and never tell you the client
was speaking the wrong unit.
The same applies to pan (-1.0 full left … +1.0 full right) and trim
(the pre-fader input gain, linear, 1.0 nominal, 0.1–16.0). Out of range
is an error, every time.
What the fields mean
| Field | What it is |
|---|---|
muted |
The channel's gate. Independent of volume — the fader keeps its position underneath |
volume |
The channel fader. LINEAR, 0.0–10.0, 1.0 = unity (0 dB). Not decibels |
pan |
Stereo position, -1.0 full left … +1.0 full right |
soloed |
The operator's solo latch, shared across everyone in the studio. A mixing-desk intent flag — it does not by itself mute the other channels, so do not read it as "everything else is off" |
trim |
Pre-fader input gain, where a hot or quiet input is brought to a nominal level before the fader. LINEAR, 1.0 nominal |
muteGroups |
Which mute groups (1–4) this channel belongs to. Read-only here — the group master button that gives membership its meaning lives in the studio's mixer panel, so there is nothing on this API a membership could be triggered by |
active |
Whether the operator has this source on the canvas. A different question from muted, and the one that explains a strip reading muted because the source was switched off — see below |
The master strip
curl -s -X PATCH \
https://api-dev.traxstreaming.live/v1/studios/$STUDIO/audio/master \
-H "Authorization: Bearer $TRAX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "muted": true }'
The master is the level and gate the whole broadcast mix passes through on its way out. Muting it silences the program for every viewer on every destination while leaving each source's own strip alone. It is the "kill the whole mix" control; to silence one contributor, patch that source instead.
It has a fader and a mute and nothing else — no pan, no solo, no trim. That is the actual shape of the master strip in the studio, and there was no reason to invent the rest.
Muted because switched off
Turning a source off in the studio auto-mutes it, and turning it back on
restores the mute it had before. So a strip can read "muted": true for two
different reasons: someone muted it, or the source is not on the canvas.
active is what tells them apart.
This matters if you are drawing a mute button: on an inactive source, the mute
you are showing is a consequence of it being off, and unmuting it over this API
will be overwritten the next time it is switched on. Read active first.
When it does not work
| Response | What happened |
|---|---|
400 invalid_request |
A value out of range (see the units above), or a field the endpoint does not accept. mute is not the spelling — it is muted. muteGroups is read-only |
403 forbidden |
The key lacks audio:read (to read) or audio:write (to change). A key with every sources:* scope still cannot reach the mixer — managing inputs and controlling the mix are separate powers |
404 not_found |
The studio is not one you can see, or the source is not in it. The two are deliberately indistinguishable |
503 unavailable |
Writes are disabled on this deployment, or the studio service is briefly unreachable. Retry |
Where to go next
- Stream to a live input — get audio into the studio in the first place.
- Drive the canvas — the visual half of the same idea.
- The /v1 reference — every endpoint and scope.