Serve a locally cached artwork image.
const url = 'http://localhost:8080/v1/artwork/example?w=96';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/artwork/example";
let querystring = [ ("w", "96"), ];
let mut headers = reqwest::header::HeaderMap::new(); headers.insert("Authorization", "Bearer <token>".parse().unwrap());
let client = reqwest::Client::new(); let response = client.get(url) .query(&querystring) .headers(headers) .send() .await;
let results = response.unwrap() .json::<serde_json::Value>() .await .unwrap();
dbg!(results);}curl --request GET \ --url 'http://localhost:8080/v1/artwork/example?w=96' \ --header 'Authorization: Bearer <token>'UI-P7. DELIBERATELY UNAUTHENTICATED: a browser cannot attach a bearer token to an , so the URL is instead a capability - the path is a 256-bit content hash, unguessable, and reveals nothing about the app, the user or the catalogue. Responses carry X-Content-Type-Options: nosniff and an immutable long cache (the bytes at a content-addressed URL can never change). Art is always served from here and NEVER hotlinked from the provider: a self-hosted deployment must not depend on a third party at browse time, and hotlinking would report the whole library to that third party on every page view.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”The cached blob’s CONTENT-ADDRESSED name: the lowercase hex SHA-256 of the image bytes plus an extension derived from the sniffed type. Nothing a remote supplies influences it, so path traversal is impossible by construction; anything not matching the pattern is 404, never 500.
Query Parameters
Section titled “Query Parameters”Amendment 37 (#707): a copy for a tile this many pixels wide. Aspect ratio kept; the smallest copy that still covers a w x 1.5w (2:3) box, so a wider image stays wider than w. Any other value (empty, repeated, or non-canonical such as 0320) is 404, as is a query that names w but does not parse (w=320;x, w=%zz). A query that names no w is ignored, as before. The original bytes are served when the image is no wider than w or no taller than 1.5w, and when the copy would not be smaller in bytes: a response is never larger than the original. Content-Type names the bytes served: a scaled copy is image/jpeg when opaque and image/png when it has transparency, whatever the stored type. Unauthenticated and nosniff as the original, and immutable except for the original served after a failed resize, which has a short max-age.
Responses
Section titled “Responses”The image, or its sized variant when w is given.
No such resource.
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, host_not_ready, restart_required, stale_revision, unsupported_source, approval_superseded, approval_expired, attempt_conflict, cancel_too_late, retry_exhausted, home_conflict, placement_ineligible, inherited_placement, recovery_uncertain, 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. Also omitted when the holder is the caller’s parked session or released runtime (amendment 39). 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
Amendment 40 (#602), ADDITIVE: a machine-readable cause under code. On 503 no_host_available, home_host_unavailable: the launch is pinned to the host that holds the caller’s home for this app (Amendment 53: or it is pinned to the host of the caller’s Steam home for a sharing launch) and that host is offline or no longer enrolled. Only an admin clears it, so a client does not spend its automatic retry on it. Names no host. Amendment 53 (#1001), on 409 home_in_use: steam_library_open_in_desktop (the holder mounts the Steam library as a shared home) or steam_game_running (the refused launch shares the library and a Steam session holds it); absent when the holder is already stopping. Open string: a client treats an unknown value as absent.
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" } ], "reason": "example" }}