Skip to content

Update the caller's client UI preferences. Partial: absent fields are left unchanged; unknown keys are preserved verbatim.

PATCH
/v1/me/ui-preferences
curl --request PATCH \
--url http://localhost:8080/v1/me/ui-preferences \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "session_overlay": { "strip_preset": "full", "strip_items": { "signal": true, "identity": true, "codec": true, "metrics": true, "hint": true, "capture": true, "exit": true, "mic": true, "fullscreen": true }, "strip_position": "top", "strip_auto_hide": "on_capture" } }'
Media typeapplication/json

Per-user client presentation preferences, synced across that user’s devices. One key per client surface so a new surface needs no new endpoint. Unknown top-level keys are preserved through a PATCH.

object
session_overlay

In-session overlay presentation. The server validates the vocabulary but never interprets the values — they only shape what the client draws.

object
strip_preset

Named starting point for strip_items. “custom” means the item set was hand-edited and no longer matches a named preset. The client, not the server, decides when a preset becomes custom.

string
Allowed values: full minimal metrics custom
strip_items

Per-item visibility. Absent keys default to visible — which is what makes adding an item additive: a client that has never written the key still gets the new item, and an older client simply ignores it. signal/identity/codec/metrics/hint are readouts; capture/exit/mic/fullscreen are ACTIONS, and a client that draws them must keep them inert while input is captured (the pointer is locked, so they cannot be clicked anyway, and drawing a live control that cannot be reached is worse than drawing none). An action item is a request to DRAW the control, never a grant of the underlying capability. mic in particular is independent of whether the session actually permits microphone capture — that is session.mic_granted, a server decision. A client honouring mic=true on a session with mic_granted=false must render the control unavailable, not hide it and not enable it.

object
signal
boolean
identity
boolean
codec
boolean
metrics
boolean
hint
boolean
capture
boolean
exit
boolean
mic
boolean
fullscreen
boolean
strip_position
string
Allowed values: top bottom left right
strip_auto_hide

Never_visible suppresses the strip entirely. The microphone “hot” indicator is NOT part of the strip and remains visible regardless — a live microphone must never become invisible.

string
Allowed values: on_capture always_visible never_visible

OK.

Media typeapplication/json

Per-user client presentation preferences, synced across that user’s devices. One key per client surface so a new surface needs no new endpoint. Unknown top-level keys are preserved through a PATCH.

object
session_overlay

In-session overlay presentation. The server validates the vocabulary but never interprets the values — they only shape what the client draws.

object
strip_preset

Named starting point for strip_items. “custom” means the item set was hand-edited and no longer matches a named preset. The client, not the server, decides when a preset becomes custom.

string
Allowed values: full minimal metrics custom
strip_items

Per-item visibility. Absent keys default to visible — which is what makes adding an item additive: a client that has never written the key still gets the new item, and an older client simply ignores it. signal/identity/codec/metrics/hint are readouts; capture/exit/mic/fullscreen are ACTIONS, and a client that draws them must keep them inert while input is captured (the pointer is locked, so they cannot be clicked anyway, and drawing a live control that cannot be reached is worse than drawing none). An action item is a request to DRAW the control, never a grant of the underlying capability. mic in particular is independent of whether the session actually permits microphone capture — that is session.mic_granted, a server decision. A client honouring mic=true on a session with mic_granted=false must render the control unavailable, not hide it and not enable it.

object
signal
boolean
identity
boolean
codec
boolean
metrics
boolean
hint
boolean
capture
boolean
exit
boolean
mic
boolean
fullscreen
boolean
strip_position
string
Allowed values: top bottom left right
strip_auto_hide

Never_visible suppresses the strip entirely. The microphone “hot” indicator is NOT part of the strip and remains visible regardless — a live microphone must never become invisible.

string
Allowed values: on_capture always_visible never_visible
Example
{
"session_overlay": {
"strip_preset": "full",
"strip_position": "top",
"strip_auto_hide": "on_capture"
}
}

Malformed or invalid request.

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"
}
]
}
}

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"
}
]
}
}