The app-image catalog with per-instance install + per-host presence state.
const url = 'http://localhost:8080/v1/admin/images';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/images";
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/images \ --header 'Authorization: Bearer <token>'Authorizations
Section titled “Authorizations”Responses
Section titled “Responses”OK.
object
The manifest schema version the cached catalog was parsed from.
The pinned quasar-images ref the catalog was fetched at (instance_settings.image_catalog_ref).
When the cached catalog was last successfully refreshed. P3: backed by instance_settings.image_synced_at, so this survives a control-plane restart.
Non-null when the last sync failed; the cached catalog is still served. P3: backed by instance_settings.image_sync_error, so this survives a control-plane restart.
Provenance of the manifest the served catalog was parsed from (#548). Recorded only on a successful sync, in the same transaction as the catalog rows, so it can never describe a manifest other than the one whose rows are stored. Backed by instance_settings.image_manifest_* (schema.md, migration 0070).
object
SHA-256 of the fetched manifest bytes the served catalog was parsed from. Never empty - the envelope carries null instead when nothing has been recorded.
The digest recorded before sha256; null until a change has ever been observed.
The resolved upstream commit the manifest was fetched at; null when ref resolution failed and the fetch fell back to the mutable ref.
The configured quasar-images ref the catalog is pinned to (instance_settings.image_catalog_ref).
The URL the manifest bytes were retrieved from.
The digest moved at the LAST sync. Self-clears on the next unchanged sync; false on a first-ever sync, which has nothing to have changed from.
When the digest last moved. Durable - unlike changed, it does not self-clear.
object
Stable manifest id; never reused for a different image.
Prebuilt = pull a registry ref; template = build locally (later phase).
Upstream’s version for this entry; an update is available when this exceeds installed_version.
Pinned image reference for a prebuilt entry (never a floating tag).
P3 (#440). Content-digest form (name@sha256:<64hex>) resolved from registry_ref’s tag at sync; empty/null when the last sync could not resolve it — installing this image is then refused (409 digest_unresolved) until a later sync resolves it. Install/update adopt this form, never the mutable registry_ref tag.
object
Set when this image is a library provider’s canonical image (e.g. steam).
P3: real. A pinned image is never auto-updated under any policy (auto sync or explicit …/update, both 409 while pinned).
P3. True when installed without dispatching ensure-everywhere; hosts pull this image on first launch placement instead.
P5. The managed runtime_presets row materialized from this image’s manifest runtime block at install; null when not installed or the image carries no runtime block. Makes the installed image launchable.
Per-host presence (present from the phase that ships host_images; may be empty in the read-only P1 surface).
object
Example
{ "images": [ { "kind": "prebuilt", "hosts": [ { "state": "absent" } ] } ]}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" } ] }}