List hosts + capacity (admin).
const url = 'http://localhost:8080/v1/hosts';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/hosts";
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/hosts \ --header 'Authorization: Bearer <token>'Authorizations
Section titled “Authorizations”Responses
Section titled “Responses”OK.
object
object
object
Whether the latest hardware capacity report is authoritative and schedulable.
Operator-facing reason when capacity detection is unavailable or failed.
One host-readiness check (first-run-experience §S1). The check set is agent-owned - a consumer must pass an unrecognized status through rather than reject it.
object
Stable machine key, e.g. “nvidia_egl_vendor_json”.
Known values: “pass”, “fail”, “skip” — “skip” means “not applicable to this host” (an NVIDIA check on an AMD box), never “we could not tell”. DELIBERATELY NOT AN ENUM: the check set (and its status vocabulary) is agent-owned and forward-compatible, so a closed schema type would force every generated client to reject a value the contract requires it to pass through. Consumers MUST render/store an unrecognized value rather than reject it.
One sentence an operator can act on, in plain language.
Exact commands to fix it, distro-aware where cheaply knowable. Empty for pass/skip.
When the stored readiness value last changed; null until reported.
Summed over this host’s REPORTED GPUs. Null - not a zeroed object - when the host has no schedulable GPUs to sum: none reported yet, or capacity_detection is not ok, which is the same condition under which GET /v1/hosts/{id}/gpus returns an empty list. “Nothing to say” and “zero capacity” are different facts and a fleet gauge must not draw the first as the second.
object
Sum of slots_total over the host’s reported GPUs.
Sum of slots_reserved: encode slots held by sessions in a reservation-holding state (assigned, starting, running, stopping).
Sum of vram_mb_total.
Sum of the LIVE-SAMPLED per-GPU vram_mb_used (the same figure the per-GPU route serves), counting only GPUs that have a sample; 0 when none has. It is deliberately NOT the declared vram_mb_reserved accounting, which #383 removed from admission and which is permanently 0 for every session created since.
Sessions in a reservation-holding state on this host’s GPUs.
How many reported GPUs the figures above are summed over. Never 0 (the object is null instead).
Example
{ "items": [ { "status": "online", "capacity_detection": "ok" } ]}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" } ] }}