guides

Set YouTube broadcast settings from your app

A YouTube destination can carry a broadcast template — the settings TRaX stamps on the broadcast it auto-creates for you at go-live: title, privacy, latency, DVR, and the handful of YouTube switches that are only settable through the API. This guide is how a mobile client reads and writes that template over /v1, so the app can offer the same broadcast controls the studio does.

Which host. https://api.traxstreaming.live/v1, with a live key or a signed-in user's token. These endpoints are in production.

The one field

The template rides on the destination as broadcastConfig. You set it when you create a destination:

POST /v1/studios/{studioId}/destinations
Authorization: Bearer sk_live_…
Content-Type: application/json

{
  "name": "YouTube main",
  "platform": "youtube",
  "connectionId": "…",
  "broadcastConfig": {
    "mode": "create",
    "title": "Tonight's show",
    "privacy": "unlisted",
    "latency": "low",
    "autoStart": true,
    "autoStop": true,
    "dvr": true,
    "madeForKids": false
  }
}

…or change it later. Omitted template fields retain their saved values; this example changes only mode, title and privacy:

PATCH /v1/studios/{studioId}/destinations/{destinationId}
Authorization: Bearer sk_live_…
Content-Type: application/json

{ "broadcastConfig": { "mode": "create", "title": "New title", "privacy": "public" } }

For a new template, omitted autoStart, autoStop and dvr default to true. Send false explicitly to disable one. An explicit empty string clears a string value; omission preserves it.

Both need the destinations:write scope — the same scope that creates the destination. A broadcastConfig on a non-YouTube destination is stored and ignored; only YouTube reads it at go-live.

The fields

Field What it sets
mode create = reuse this destination's previously created broadcast while it remains streamable, or create one from this template when there is no usable saved broadcast. pick = use the exact selected broadcast; auto-create is off.
title Title stamped on the auto-created broadcast.
privacy public, unlisted, or private. There is no members-only — YouTube's live API does not expose it.
description Broadcast description body.
latency normal, low, or ultraLow. ultraLow disables closed captions and caps resolution at 1080p.
autoStart / autoStop The broadcast goes live / stops automatically with your ingest.
dvr Viewers can rewind the live stream.
madeForKids The COPPA "made for kids" self-declaration.
enableEmbed May the watch page be embedded off YouTube.
recordFromStart Archive records from the first frame.
enableMonitorStream The preview/monitor stream that parks the broadcast in "testing" before it goes live.
streamDelayMs Intentional broadcast delay, in milliseconds. 0 = none.
projection rectangular or 360.

Create mode does not search the channel for another studio's live or scheduled broadcast to adopt. A failed lookup is an error, not permission to create a replacement: temporary provider failures must be retried. A confirmed missing or ended saved broadcast can be replaced in create mode.

Two rules worth knowing

Optional fields. enableEmbed, recordFromStart, and enableMonitorStream distinguish an explicit false from an unset value. On a new template, omit one to leave it unset and use the provider/default behavior. On PATCH, omission preserves the existing value, including an explicit false; it does not reset that value. Monitor settings affect the preview/testing stage; autoStart controls whether YouTube starts automatically.

Immutable once live. YouTube freezes most of the broadcast body (embed, DVR, record-from-start, monitor stream, delay, projection) the moment the broadcast enters testing or live. Edit the template while you are off air; a change sent after go-live applies to the next broadcast, not the running one.

Round-trip

A read of the destination shows the template back to you (the stream key is never returned):

GET /v1/studios/{studioId}/destinations

The broadcastConfig you get back is exactly what you saved. Omitted tri-state fields stay omitted — the read never invents a false you did not send.

Check what go-live will actually do

What you saved is not the whole story, and the gap has cost people streams. Several defaults are applied at go-live rather than stored, so they are absent from the read above: an omitted autoStart means true, an omitted latency means normal, an omitted title becomes "TRaX Stream", an omitted privacy becomes unlisted, and an omitted scheduled start becomes thirty seconds from whenever you go live. And whether a broadcast gets created at all depends on mode, on whether one is picked, and on how recently one was auto-created — together, not separately.

One read answers both:

GET /v1/studios/{studioId}/destinations/{destinationId}/next-broadcast

Requires destinations:read. It writes nothing, calls no YouTube API and spends no quota, so it is safe to call every time a settings screen opens.

{
  "destinationId": "…",
  "mode": "create",
  "action": "create",
  "broadcastId": "",
  "reason": "Going live will create a new YouTube broadcast from these settings, bind it to the stream key and select it.",
  "effective": { "title": "TRaX Stream", "privacy": "unlisted", "latency": "normal",
                 "autoStart": true, "autoStop": true, "dvr": true, "madeForKids": false,
                 "projection": "", "streamDelayMs": 0, "description": "", "scheduledStartTime": "" },
  "defaulted": ["title", "privacy", "latency", "autoStart", "autoStop", "dvr", "scheduledStartTime"],
  "warnings": [],
  "estimatedQuotaUnits": 102,
  "quotaEstimateInferred": true
}

effective is what will be sent. defaulted names the fields nobody chose, so you can show them differently from the ones somebody did — autoStart: true because it was chosen and autoStart: true because it was never set are the same value and different facts.

action is what go-live will do:

action Meaning
create A new broadcast will be created from effective.
reuse A broadcast created moments ago will be used instead, so a retried go-live does not leave a duplicate on the channel. If YouTube says it has already ended, a fresh one is created.
pick The broadcast you picked will be used. Nothing is created.
legacy No broadcast at all — go-live falls back to the channel's default stream key and nothing appears in YouTube Studio. Always comes with a warnings entry saying how to fix it.
none Not a broadcast-capable destination: a pasted RTMP key, or a platform with no broadcast concept.

estimatedQuotaUnits is our estimate, not YouTube's published figure — Google documents no per-method quota costs for the Live Streaming API. quotaEstimateInferred is always true and is there so the caveat travels with the number. Show them together or show neither.

Work with broadcasts directly (#665)

The template above tells go-live what to create. Three more endpoints let your app skip the template entirely and deal in actual YouTube broadcasts — the same three moves the studio's Stream Setup panel makes.

List what you can pick from

GET /v1/studios/{studioId}/destinations/{destinationId}/broadcasts

Requires destinations:read. Returns the destination's platform-side broadcasts — recent, upcoming and active — each with its id, title, lifecycle status and, on YouTube, the broadcast's live settings (privacy, latency, DVR, …) so an editor can seed from actual values instead of guesses. Platforms with no broadcast concept (Twitch, Kick) and pasted-key destinations answer an empty list, not an error.

Create a broadcast now

POST /v1/studios/{studioId}/destinations/{destinationId}/broadcast
Content-Type: application/json

{ "title": "Tonight's show", "description": "…" }

Requires destinations:write. This inserts the broadcast on YouTube immediately — not at go-live — seeded from the destination's saved broadcastConfig (privacy, latency, DVR, contentDetails), with your title/ description overriding the template's copies. Omit scheduledStartTime to start now; pass an RFC 3339 time to schedule it. The created broadcast is adopted as the destination's pick (mode flips to pick), so going live binds the stream to it. 201 returns the broadcast; its id is what the edit, thumbnail and pick endpoints take.

Pick an existing broadcast — or clear the pick

POST /v1/studios/{studioId}/destinations/{destinationId}/broadcast/pick
Content-Type: application/json

{ "broadcastId": "dQw4w9WgXcQ", "title": "Tonight's show" }

Requires destinations:write. Points the destination at one existing broadcast from the list above; go-live then binds the stream to it. Send { "broadcastId": null } to clear the pick — the destination returns to create mode and go-live follows the saved-broadcast reuse or creation rules above. This is a pointer write: nothing is validated against YouTube here, so a deleted broadcast id surfaces at go-live, not at pick time.

If a picked broadcast has ended or is no longer available, go-live reports that selection's error. It does not substitute another event from the channel. Pick another broadcast or switch to create mode before retrying.

Stream keys

A stream key is the reusable RTMP ingest target a broadcast binds to. On YouTube these are the channel's liveStreams. Four endpoints let your app manage them — the same operations the studio's Stream keys panel performs.

The ingest secret is never returned. A stream key comes back carrying only its keyLast4 — a 4-character recognition tail so a human can tell two keys apart. There is no field for the ingest key itself on any read; TRaX resolves the real credential server-side at go-live. YouTube-only: channel-scoped platforms (Twitch, Kick) and pasted-key destinations answer an empty list from the list endpoint and 400 invalid_request from the mutations.

List, create, edit, delete

GET    /v1/studios/{studioId}/destinations/{destinationId}/stream-keys
POST   /v1/studios/{studioId}/destinations/{destinationId}/stream-keys
PATCH  /v1/studios/{studioId}/destinations/{destinationId}/stream-keys/{keyId}
DELETE /v1/studios/{studioId}/destinations/{destinationId}/stream-keys/{keyId}

The list requires destinations:read; the three writes require destinations:write. Each key reports its streamStatus, its reusable and isDefault flags, its resolution and frameRate, and keyLast4.

Create takes an optional title, resolution and frameRate; leave any of them empty for a server default (variable accepts any ingest). Edit is a PATCH — an empty field is left unchanged.

An active key cannot be edited or deleted. While a key is receiving video its streamStatus reads active, and both PATCH and DELETE refuse it with 409 — stream_key_active on edit, stream_key_bound on delete of a key a broadcast still points at. Stop the ingest, or unbind the broadcast (below), then retry.

Completing an incomplete broadcast

A broadcast created with no key bound is incomplete: on YouTube its lifecycle is created, its boundStreamId is empty, and it cannot receive video until a key is bound. Bind one to complete it:

POST /v1/studios/{studioId}/destinations/{destinationId}/broadcast/bind
Content-Type: application/json

{ "broadcastId": "dQw4w9WgXcQ", "streamKeyId": "abcd-1234" }

Requires destinations:write. It returns the broadcast's resulting boundStreamId. Send an empty (or omitted) streamKeyId to unbind — the broadcast returns to incomplete. Binding is the one moment a key is attached to a broadcast: TRaX never auto-binds a key onto a broadcast it did not create. A broadcast you picked from the channel keeps whatever key it already had, so if it comes back with an empty boundStreamId, bind one here before going live.

Rounding out the set, the PATCH …/broadcast edit and the thumbnail upload from the Broadcast Manager wave apply to whichever broadcast id you name — see the API reference for updateDestinationBroadcast and setDestinationBroadcastThumbnail.

Which broadcast am I on? activeBroadcastId

While a destination is attached with a resolved ingest binding, this ID stays with its actual broadcast. Changing or clearing the saved pick configures the next attachment; it does not retarget the current output or its lifecycle checks. Older sessions without a binding have unknown lifecycle until the destination is reattached. The saved associations below describe idle or older unbound sessions.

Every one of the calls above takes a broadcast id, and until now there was no supported way to get one for a destination you did not just create. Every destination read now carries it:

GET /v1/studios/{studioId}/destinations
{
  "id": "…",
  "platform": "youtube",
  "activeBroadcastId": "dQw4w9WgXcQ"
}

activeBroadcastId is the broadcast this destination is currently operating on. It is what you pass to PATCH …/broadcast and to the thumbnail upload. It rides the SSE destination event too, so a client watching GET /v1/studios/{studioId}/events learns a new id the moment go-live mints one instead of polling for it.

The field is always present and empty when there is no broadcast: a channel-scoped platform (Twitch, Kick) has no broadcast concept at all, a create-mode YouTube destination that has never gone live has nothing yet, and a cleared pick leaves nothing behind.

Two cases, and they do not age the same. Treat this difference as part of the contract, not a detail:

How the id was obtained How long it stays valid
Picked — you called …/broadcast/pick, or created one with POST …/broadcast Stable until the pick changes or is cleared. Names a real broadcast whether or not anything is streaming.
Auto-created — no pick; the destination mints a fresh broadcast at each go-live The one minted for the current or most recent session. Goes stale once that broadcast completes — a completed YouTube broadcast cannot be edited or restreamed, and the next go-live mints a different one.

So: read it again after going live. Do not cache it across sessions and assume an edit will land where you expect — between sessions, a create-mode id can name a broadcast the next go-live will not touch. We would rather ship a field that tells you when not to trust it than one that quietly lies.

Do not read settings

Destinations also carry a settings blob, and the id does appear inside it. That is not a contract. settings is opaque: its schema is not part of this API, we reshape it when the studio needs to, and a client parsing it will break on a release that changes nothing you can see. activeBroadcastId exists precisely so you never have to.

A typical flow, end to end: list the broadcasts you can pick from, pick one or create one now, then read activeBroadcastId off the destination whenever you need to name it again.

Start and verify YouTube is live

  1. Link the YouTube account and confirm its connection is active. The channel must have live streaming enabled and the linked grant must authorize YouTube broadcast management. Reconnect the existing connection when authorization has actually been revoked; a temporary provider/network error is retryable and does not require relinking.
  2. Create an enabled YouTube destination with a create template, or pick a streamable broadcast from the destination's broadcast list. Saving a template or creating a scheduled broadcast does not send video or make it live.
  3. Save sources, layout and output settings first (PATCH /v1/studios/{studioId}/output changes the next session’s format). Call POST /v1/studios/{studioId}/go-live without a request body, using stream:golive scope. It uses saved studio state; a body is rejected. Inspect destinationFailures, skippedDuplicates and destinations in the response. HTTP 200 means the encoder session was accepted; a valid program-only start may have no armed destination IDs.
  4. Poll GET /v1/studios/{studioId}/destinations. Read status and lastError for delivery, then broadcastLifecycle, broadcastError and broadcastObservedAt for YouTube. status: "LIVE" by itself only reports encoder delivery. broadcastLifecycle: "live" confirms YouTube was live at the observation time; an absent value means it has not been confirmed.

With autoStart: false, TRaX waits until YouTube reports the bound ingest active before requesting a manual transition. A monitor-enabled broadcast goes through testing before live; the accepted transition request is followed by a provider readback. Startup retries are bounded to three minutes of reconciliation while the destination is active. A terminal error or exhausted budget appears in broadcastError; follow that guidance and disable/re-enable the destination or stop/start the session after correcting the cause. Stopping cancels local pending work; a request already accepted by YouTube cannot be recalled.

A requested privacy change must be confirmed before TRaX reuses that broadcast's ingest. If confirmation fails, that destination is reported as failed rather than continuing with a known different privacy setting. Other destinations and a valid program session can still start, so inspect the per-destination results.

These fields describe the API version served by the deployment you call. Check its /v1/openapi.json; older deployments may not yet expose the additive result and lifecycle fields.

YouTube's transition requirements require active bound ingest before testing/live, and its OAuth guidance describes authorization and token refresh. Channel eligibility and provider errors remain separate from an accepted TRaX encoder start.