Node-agent internal: claim pending Steam library scans for this host. (Not operator-facing.)
const url = 'http://localhost:8080/v1/agent/library/scan-pending';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/agent/library/scan-pending";
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/agent/library/scan-pending \ --header 'Authorization: Bearer <token>'Phase 4. Claims up to 50 pending scans for the calling host with FOR UPDATE SKIP LOCKED, so two agents polling simultaneously get DISJOINT sets rather than blocking on each other. An empty list is the steady state, and is also what this returns when discovery is switched off - the master switch is re-read HERE as well as in the scheduler, because the scheduler is what stops rows being created and this is what stops an already-queued row being handed out after an operator turned the feature off. relative_roots and the two bounds are control-plane-supplied rather than agent constants, so a bound can be tightened without an agent release. root_path is control-plane-supplied and the agent validates containment against its own configured homes root before walking it - a path outside that root is refused and reported as an error, never walked.
Authorizations
Section titled “Authorizations”Responses
Section titled “Responses”OK.
object
Empty is the steady state, and is also the answer when discovery is switched off.
One scan job, exactly as it goes on the wire. NOTE WHAT IS ABSENT: no user id, no username, no user-derived field. root_path is an opaque home directory and which person it belongs to is resolved control-plane-side on receipt of the report and never leaves that process. This is agent-api.md’s P2-01 verdict - per-user concerns never reach the agent - honoured literally, and it is a GUARANTEE OF THE INTERFACE rather than a property of the current implementation.
object
The only handle the agent ever holds.
The absolute host path of the user’s managed home. Control-plane-supplied, so the agent VALIDATES CONTAINMENT against its own configured homes root before walking it: a path outside that root is refused and reported as an error, never walked. The agent does not trust the control plane with a filesystem path any more than it would trust a client.
In-home paths to glob for appmanifest_*.acf, today the flatpak/deb (.local/share/Steam/steamapps) and classic (.steam/steam/steamapps) layouts, which are usually symlinks to each other and are de-duplicated by resolved path. libraryfolders.vdf is deliberately NOT consulted: it records CONTAINER-side paths, meaningless to a host-side scanner, so translating it would be guesswork - libraries on other disks are an accepted limitation instead.
Cap on manifests per scan (512 today).
Per-file read cap (1 MiB today). Both bounds are CONTROL-PLANE-OWNED rather than agent constants, so they can be tightened without an agent release.
Examplegenerated
{ "scans": [ { "scan_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "root_path": "example", "relative_roots": [ "example" ], "max_entries": 1, "max_manifest_bytes": 1 } ]}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" } ] }}Could not list pending scans.
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" } ] }}