Set the WHOLE entitlement state of a provider app (all|user|none), by provider name.
const url = 'http://localhost:8080/v1/admin/library-providers/example/entitlement-mode';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"mode":"all"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}use serde_json::json;use reqwest;
#[tokio::main]pub async fn main() { let url = "http://localhost:8080/v1/admin/library-providers/example/entitlement-mode";
let payload = json!({"mode": "all"});
let mut headers = reqwest::header::HeaderMap::new(); headers.insert("Authorization", "Bearer <token>".parse().unwrap()); headers.insert("Content-Type", "application/json".parse().unwrap());
let client = reqwest::Client::new(); let response = client.post(url) .headers(headers) .json(&payload) .send() .await;
let results = response.unwrap() .json::<serde_json::Value>() .await .unwrap();
dbg!(results);}curl --request POST \ --url http://localhost:8080/v1/admin/library-providers/example/entitlement-mode \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "mode": "all" }'#465, additive. REPLACES every entitlement row on the provider app named by {provider} with exactly what mode implies - “all” writes one (‘all’, NULL) row, “user” writes one (‘user’,
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”#465 amendment: the library_provider name (e.g. “steam”), NOT an app id - this route exists precisely because the caller does not have one yet (see the amendment note on POST /v1/admin/library-providers/{provider}/entitlement-mode). Matched case-insensitively against apps.library_provider, mirroring EnsureProviderApp’s own lower-casing on write. An empty/whitespace-only value is 400 validation_failed.
Request Bodyrequired
Section titled “Request Bodyrequired”#465. The request body for POST …/library-providers/{provider}/entitlement-mode.
object
#465. Who a provider app is for, as one exclusive state rather than an incremental grant/revoke pair. Mirrors the server-side images.EntitlementMode vocabulary (control-plane internal/images).
Responses
Section titled “Responses”Mode applied.
#465. app_id is surfaced READ-ONLY so a caller that wants to keep using the generic entitlements routes afterward (e.g. to add one more specific user) has it without a second lookup; items is the full post-write entitlement list, same shape and ordering as GET /v1/admin/apps/{id}/entitlements.
object
object
Echoes the path parameter, normalized.
#465. Who a provider app is for, as one exclusive state rather than an incremental grant/revoke pair. Mirrors the server-side images.EntitlementMode vocabulary (control-plane internal/images).
Steam library discovery Phase 2. One fact: “this subject may see and launch this app”. PRESENCE OF THE ROW IS THE FACT - there is no revoked boolean and no soft delete. An app may carry BOTH an ‘all’ row and a personal row for the same user: they are independent facts and revoking one must not revoke the other, which is why readers match on EXISTENCE and never by joining entitlements into the app query (a join would emit such an app twice and, because GET /v1/apps pages by offset with a limit+1 overfetch, shift the page boundary and silently drop an app off the far end).
object
Steam library discovery Phase 2: who an entitlement is granted to. ‘all’ = everyone (subject_id is null); ‘user’ = one account. Widening to ‘group’ is additive - a new value and a third partial unique index, no shape change - which is why an entitlement carries a subject TYPE plus id rather than a bare user_id.
The subject account; null when subject_type is ‘all’.
Joined for display; null when subject_type is ‘all’. Never accepted on the write shape.
Joined for display; never accepted on the write shape.
Steam library discovery Phase 2: PROVENANCE, NOT AUTHORITY - all three grant identical access. ‘admin’ = an operator granted it (audited). ‘provider’ = a library-discovery sync wrote it and a sync may revoke it - WRITTEN SINCE PHASE 4 (migration 0045) by the scan reconciler, carrying source_ref ‘library:steam:
The acting admin for a granted_by=‘admin’ row, null otherwise. Recorded server-side from the bearer identity and never assertable by a client. Deleting that operator NULLs this rather than revoking the entitlement.
Free-form provenance for a ‘provider’ grant. ‘’ for everything Phase 2 writes; Phase 4’s reconciler writes ‘library:<external_source>:<external_id>’ (e.g. ‘library:steam:620’). It is provenance for a human reading a row, NOT a key anything joins on.
Example
{ "entitlement_mode": { "mode": "all", "items": [ { "subject_type": "user", "granted_by": "admin" } ] }}Amendment 21. No provider app yet; the mode is stored and applied when the app is created.
Amendment 21 (#490). The 202 body of POST …/library-providers/{provider}/entitlement-mode: the mode is stored and EnsureProviderApp grants it when it creates the provider app. There is no app_id or items yet.
object
object
Echoes the path parameter, normalized.
#465. Who a provider app is for, as one exclusive state rather than an incremental grant/revoke pair. Mirrors the server-side images.EntitlementMode vocabulary (control-plane internal/images).
Example
{ "pending_entitlement_mode": { "mode": "all" }}Malformed or invalid request.
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" }}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, 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" }}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, 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" }}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" }}