Read instance settings (registration_mode, …).
const url = 'http://localhost:8080/v1/admin/settings';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}use reqwest;
#[tokio::main]pub async fn main() { let url = "http://localhost:8080/v1/admin/settings";
let mut headers = reqwest::header::HeaderMap::new(); headers.insert("Authorization", "Bearer <token>".parse().unwrap());
let client = reqwest::Client::new(); let response = client.get(url) .headers(headers) .send() .await;
let results = response.unwrap() .json::<serde_json::Value>() .await .unwrap();
dbg!(results);}curl --request GET \ --url http://localhost:8080/v1/admin/settings \ --header 'Authorization: Bearer <token>'Authorizations
Section titled “Authorizations”Responses
Section titled “Responses”OK.
object
object
Managed-home backing store: auto = local when the session host has an effective home root, volume otherwise. Affects new homes only.
P3 (image management). Instance-wide update policy applied by POST /v1/admin/images/sync. Column exists since migration 0054 (DDL default notify) but only reaches the wire from P3. Optional in the envelope so pre-P3 servers stay conformant.
Microphone capture amendment (2026-08-02). Instance-wide gate for the POST /v1/sessions mic request. Default false (ship-dark). Server-enforced at launch; flipping it affects subsequent launches only.
Admin-libraries amendment (2026-08-01). The operator-set scan interval, minutes, default 360. THE DATABASE COLUMN IS THE OPERATOR PATH AND THE ENV VAR IS AN OVERRIDE, NOT A DEFAULT: when QUASAR_LIBRARY_SCAN_INTERVAL is set it wins - and its documented 0 = hard-kill-regardless-of-the-database-flag semantics are unchanged. LibraryStatus.scan_interval_secs remains the RESOLVED value (database-then-env-override), and LibraryStatus.interval_overridden_by_env says which source won so a UI can grey a control the environment has pinned.
Admin-libraries amendment (2026-08-01). The operator-set third-party appdetails lookup switch, default false - the same privacy decision QUASAR_STEAM_APPDETAILS_LOOKUP carried, now settable from the admin UI. Same override rule as the interval: a SET env var wins (a privacy-hardened deployment can pin it off in the environment), and LibraryStatus.appdetails_overridden_by_env reports when that happened.
Steam library discovery Phase 4 (migration 0045). THE MASTER SWITCH, AND THE ONLY SWITCH. Default false - ship-dark, the posture artwork already holds - and with it false the scheduler returns before its first query and the agent pull answers an empty list: no scan rows, no agent work, no third-party calls. Under operator decision 1 AUTO-PUBLISH IS THE BEHAVIOUR, NOT A MODE (there is no review queue), so there is deliberately no second “publish” toggle: its false branch would select a path that was never built. Read PER PASS and per request rather than cached at boot, so flipping it in the admin UI takes effect without a restart.
First-run wizard v2 §S6e (migration 0064). The admin-editable signaling origin allow-list, normalized (scheme + lowercased host). THIS IS THE DATABASE COLUMN, NOT NECESSARILY WHAT /v1/signal ENFORCES: QUASAR_ALLOWED_ORIGINS, when SET, overrides it outright - including when set to the empty string, which is how a hardened deployment pins the list off. That override rule is what makes the migration a behavioural no-op on upgrade for every existing deployment. GET /v1/admin/access-check reports the RESOLVED list plus which source won, so a UI can grey out a control the environment has pinned - the same shape library_discovery_interval_minutes uses. AN EMPTY LIST IS NOT “DENY ALL”: /v1/signal still admits a same-origin request and a request with no Origin header at all, so a fresh instance with nothing configured works. Optional in the envelope so pre-amendment servers stay conformant.
Example
{ "settings": { "registration_mode": "closed", "storage_provider": "auto", "image_update_policy": "manual" }}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" } ] }}Authenticated but insufficient role / not the owner (precedes resource lookup).
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" } ] }}