Update the caller's client UI preferences. Partial: absent fields are left unchanged; unknown keys are preserved verbatim.
const url = 'http://localhost:8080/v1/me/ui-preferences';const options = { method: 'PATCH', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"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"}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}use std::str::FromStr;use serde_json::json;use reqwest;
#[tokio::main]pub async fn main() { let url = "http://localhost:8080/v1/me/ui-preferences";
let payload = json!({"session_overlay": json!({ "strip_preset": "full", "strip_items": json!({ "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" })});
let mut headers = reqwest::header::HeaderMap::new(); headers.insert("Authorization", "Bearer <token>".parse().unwrap()); headers.insert("Content-Type", "application/json".parse().unwrap());
let client = reqwest::Client::new(); let response = client.request(reqwest::Method::from_str("PATCH").unwrap(), url) .headers(headers) .json(&payload) .send() .await;
let results = response.unwrap() .json::<serde_json::Value>() .await .unwrap();
dbg!(results);}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" } }'Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”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
In-session overlay presentation. The server validates the vocabulary but never interprets the values — they only shape what the client draws.
object
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.
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
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.
Responses
Section titled “Responses”OK.
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
In-session overlay presentation. The server validates the vocabulary but never interprets the values — they only shape what the client draws.
object
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.
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
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.
Example
{ "session_overlay": { "strip_preset": "full", "strip_position": "top", "strip_auto_hide": "on_capture" }}Malformed or invalid request.
object
object
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.
Present on restart_required.
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.
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.
object
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.
object
object
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.
Present on restart_required.
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.
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.
object
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" } ] }}