guides

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/v1 with a dev key. This page will say so when they reach production.

Three calls, and the read is the one you start with:

  1. GET /v1/studios/{id}/audio returns the whole mix — the master strip plus one strip per source.
  2. PATCH /v1/studios/{id}/audio/sources/{sourceId} moves one channel.
  3. PATCH /v1/studios/{id}/audio/master moves 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:read to see the mix and audio:write to change it. audio:write includes 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.116.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.010.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

  1. Stream to a live input — get audio into the studio in the first place.
  2. Drive the canvas — the visual half of the same idea.
  3. The /v1 reference — every endpoint and scope.