Skip to content

UI-P4: list launch profiles, each with its ordered rungs and its used_by.

GET
/v1/admin/launch-profiles
curl --request GET \
--url http://localhost:8080/v1/admin/launch-profiles \
--header 'Authorization: Bearer <token>'

OK.

Media typeapplication/json
object
items
required
Array<object>
object
id
required
string
display_name
required
string
description
required
string
visibility
required

Debug/internal launch profiles are never returned by GET /v1/me/profiles.

string
Allowed values: user debug internal
sort_order
required
integer
rungs
required
Array<object>
>= 1 items
object
position
required

Preference order, 1 = tried first. Server-assigned from the ordered id array on write; a client never sends positions.

integer
>= 1
stream_profile
required

ONE ENCODE RUNG (UI-P4): a single codec at a single resolution, frame rate and bitrate. NOT user-facing - a user picks a LaunchProfile, which lists these in preference order. BREAKING vs the pre-UI-P4 shape: codecs[] and its launchable|future|unsupported status enum are GONE, replaced by a single codec. A rung IS a codec, so there is nothing left for a status to describe; a codec is offered because a rung using it exists in a launch profile, and withdrawn by removing that rung.

object
id
required

Rungs created by migration 0036’s fan-out use ‘-’, e.g. ‘1080p60-h264’.

string
display_name
required
string
codec
required

The CATALOG codec vocabulary used by stream profiles (rungs). Note ‘hevc’, not the wire ‘h265’ - the rename is bridged in exactly one place server-side and never on this surface.

string
Allowed values: h264 hevc av1
width
required
integer
height
required
integer
fps
required
integer
h264_profile
required

Meaningful only when codec is h264. The browser (WebRTC) receiver rejects High on both VA and NVENC, so a browser launch still negotiates down to constrained-baseline; this records the rung’s preference for a capable client.

string
nominal_bitrate_kbps
required
integer
min_offer_bandwidth_kbps
required
integer
recommended_offer_bandwidth_kbps
required
integer
headroom_factor
required
number
abr_floor_kbps
required
integer
max_startup_rtt_ms
required
integer
min_decode_height
required
integer
high_refresh_display
required
string
Allowed values: none recommended required
hardware_encoder_required
required
boolean
browser_client
required
string
Allowed values: recommended supported risky
playout0_ms
required
integer
warnings

Present only on a WRITE response (PATCH /v1/admin/stream-profiles/{id}), and omitted entirely when there is nothing to say, so every read path is byte-identical to before. Reuses UI-P4’s WriteWarning shape rather than inventing a second one. Emitted when a codec change leaves every launch profile listing this rung still carrying a launchable H.264 rung, but no longer LAST. That warns rather than rejects, because the 0036 fan-out preserved each profile’s stored codec order and H.264 is often first, so rejecting would make migrated profiles permanently uneditable. A change that would leave a chain with NO launchable H.264 rung is a 409 instead, naming the affected launch profiles: an operator editing one rung has no way to know which chains depend on it. This guard exists because the rule was previously enforced only when editing a launch profile’s rung LIST, so editing a stream profile’s codec could silently invalidate every chain listing it, with nothing surfacing until a launch failed at dispatch. See docs/design/plans/2026-07-29-library-ux-fixes-spec.md.

Array<object>

UI-P4: a non-fatal advisory returned on an admin profile write. A warning NEVER fails the request - it reports a configuration that is legal but almost certainly not what the operator meant.

object
code
required

H264_floor_not_last - every rung after the H.264 rung is unreachable, because H.264 passes every clamp. floor_not_least_demanding - the H.264 rung has a higher min_offer_bandwidth_kbps or min_decode_height than a rung above it, or requires a hardware encoder while a rung above it does not; a floor harder to satisfy than the rung above it is a misconfiguration.

string
Allowed values: h264_floor_not_last floor_not_least_demanding
message
required
string
visibility
required

Rungs created by the fan-out are ‘internal’: a rung is never offered standalone, only via the launch profile that lists it.

string
Allowed values: user debug internal
used_by

Admin read only: the launch profiles listing this rung. Shown inside the editor as well as the list, because editing a shared object changes every consumer.

Array<object>

A minimal reference to a profile object, used in ‘used by’ lists.

object
id
required
string
display_name
required
string
session_count

Admin read only, ADDITIVE, omitted when zero: how many session rows record this rung as the one they resolved to. This is the SECOND “used by” dimension and it also blocks DELETE - sessions.stream_profile_id is a plain foreign key with no ON DELETE clause (NO ACTION on purpose: ON DELETE SET NULL would erase which rung a historical session actually got, which is the entire reason the column exists), so one historical session refuses the delete at the database. A client must treat a non-zero session_count exactly like a non-empty used_by when deciding whether to offer Delete.

integer
used_by

Everything referencing this launch profile. Non-empty in any dimension means DELETE is 409.

object
apps
required
Array<object>
object
id
required
string format: uuid
name
required
string
global_default
required

True when stream_profile_policy.global_default_profile_id points here.

boolean
user_preferences
required

Count of user_profile_preferences rows pointing here.

integer
warnings
Array<object>

UI-P4: a non-fatal advisory returned on an admin profile write. A warning NEVER fails the request - it reports a configuration that is legal but almost certainly not what the operator meant.

object
code
required

H264_floor_not_last - every rung after the H.264 rung is unreachable, because H.264 passes every clamp. floor_not_least_demanding - the H.264 rung has a higher min_offer_bandwidth_kbps or min_decode_height than a rung above it, or requires a hardware encoder while a rung above it does not; a floor harder to satisfy than the rung above it is a misconfiguration.

string
Allowed values: h264_floor_not_last floor_not_least_demanding
message
required
string
Example
{
"items": [
{
"visibility": "user",
"rungs": [
{
"stream_profile": {
"codec": "h264",
"high_refresh_display": "none",
"browser_client": "recommended",
"warnings": [
{
"code": "h264_floor_not_last"
}
],
"visibility": "user"
}
}
],
"warnings": [
{
"code": "h264_floor_not_last"
}
]
}
]
}

Missing/invalid/expired/revoked token.

Media typeapplication/json
object
error
required
object
code
required

E.g. validation_failed, unauthorized, forbidden, not_found, conflict, session_quota_exceeded, home_in_use, home_not_provisioned, parent_app_disabled, profile_ineligible, profile_not_launchable_for_app, no_host_available, capacity_exhausted, restart_required, rate_limited, internal. Open string, not an enum: new codes are additive and an unknown one falls through to a client’s generic per-status branch.

string
message
required
string
live_sessions

Present on restart_required.

integer
session_id

Steam library discovery Phase 3, ADDITIVE: present on home_in_use when the guard could name the CONFLICTING live session - the one already holding the home. It is here so the client can offer “go to your running session” with a link instead of a dead-end toast. OMITTED rather than empty when the conflict is known but the session is not, so a client branches on presence and never renders a link to nowhere. Load-bearing once derived tiles exist: the lock is held by the PARENT’s home, so a user who clicks a game tile can be refused because a DIFFERENT app (the Steam launcher, or another game from the same install) is running, and without the session id the refusal reads as a bug. See control-api.md §Derived tiles.

string format: uuid
derived_tiles

Steam library discovery Phase 3, ADDITIVE: present on the 409 conflict from DELETE /v1/apps/{id} when the app has derived tiles and ?delete_derived=true was not sent. A LIST, not a count - the point of the confirmation is that the admin sees what they are about to destroy. Capped; an empty array means the tiles could not be listed, never that there are none.

Array<object>
object
id
required
string format: uuid
name
required
string
Examplegenerated
{
"error": {
"code": "example",
"message": "example",
"live_sessions": 1,
"session_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"derived_tiles": [
{
"id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"name": "example"
}
]
}
}

Authenticated but insufficient role / not the owner (precedes resource lookup).

Media typeapplication/json
object
error
required
object
code
required

E.g. validation_failed, unauthorized, forbidden, not_found, conflict, session_quota_exceeded, home_in_use, home_not_provisioned, parent_app_disabled, profile_ineligible, profile_not_launchable_for_app, no_host_available, capacity_exhausted, restart_required, rate_limited, internal. Open string, not an enum: new codes are additive and an unknown one falls through to a client’s generic per-status branch.

string
message
required
string
live_sessions

Present on restart_required.

integer
session_id

Steam library discovery Phase 3, ADDITIVE: present on home_in_use when the guard could name the CONFLICTING live session - the one already holding the home. It is here so the client can offer “go to your running session” with a link instead of a dead-end toast. OMITTED rather than empty when the conflict is known but the session is not, so a client branches on presence and never renders a link to nowhere. Load-bearing once derived tiles exist: the lock is held by the PARENT’s home, so a user who clicks a game tile can be refused because a DIFFERENT app (the Steam launcher, or another game from the same install) is running, and without the session id the refusal reads as a bug. See control-api.md §Derived tiles.

string format: uuid
derived_tiles

Steam library discovery Phase 3, ADDITIVE: present on the 409 conflict from DELETE /v1/apps/{id} when the app has derived tiles and ?delete_derived=true was not sent. A LIST, not a count - the point of the confirmation is that the admin sees what they are about to destroy. Capped; an empty array means the tiles could not be listed, never that there are none.

Array<object>
object
id
required
string format: uuid
name
required
string
Examplegenerated
{
"error": {
"code": "example",
"message": "example",
"live_sessions": 1,
"session_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"derived_tiles": [
{
"id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"name": "example"
}
]
}
}