Skip to content

Atomically edit typed policy choices by expected revision (RH05).

PATCH
/v1/admin/hosts/{id}/policy
curl --request PATCH \
--url http://localhost:8080/v1/admin/hosts/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/policy \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "expected_revision": "example", "changes": { "additionalProperty": { "source": "automatic", "value": "example" } } }'

Validates the whole edit before persistence. A successful edit commits choices, a monotone revision and reconciliation obligation in one transaction; offline intent is saved as pending. A stale edit does not write and returns the current view and changed keys. A relevant edit supersedes an unstarted idle approval. A group without confirmed typed ownership returns upgrade_required without writing anything.

id
required
string format: uuid
Media typeapplication/json
object
expected_revision
required

Canonical nonnegative decimal string; no sign, whitespace or leading zeros.

string
/^(0|[1-9][0-9]*)$/
changes
required
object
>= 1 properties
key
additional properties
object
source
required
string
Allowed values: automatic deployment explicit
value

Required for explicit; forbidden for automatic or deployment. Validated against the frozen hostcfg catalog, including cross-key groups.

Saved policy view.

Media typeapplication/json
object
revision
required

Canonical nonnegative decimal string; no sign, whitespace or leading zeros.

string
/^(0|[1-9][0-9]*)$/
choices
required
object
key
additional properties
object
source
required
string
Allowed values: automatic deployment explicit
value

Required for explicit; forbidden for automatic or deployment. Validated against the frozen hostcfg catalog, including cross-key groups.

resolved
required
object
key
additional properties
object
value
required

Resolved JSON value for this setting.

source
required
string
Allowed values: automatic deployment explicit
observed_at
required

Null when no reliable resolution evidence exists.

string | null format: date-time
evidence_id
string | null
groups
required
object
key
additional properties
object
desired_revision
required

Canonical nonnegative decimal string; no sign, whitespace or leading zeros.

string
/^(0|[1-9][0-9]*)$/
applied_revision
required
One of:

Canonical nonnegative decimal string; no sign, whitespace or leading zeros.

string
/^(0|[1-9][0-9]*)$/
desired_digest
required
One of:

Lowercase SHA-256 hex digest of canonical content.

string
/^[0-9a-f]{64}$/
applied_digest
One of:

Lowercase SHA-256 hex digest of canonical content.

string
/^[0-9a-f]{64}$/
scope
required
string
Allowed values: next_session restart
status
required
string
Allowed values: pending applied failed upgrade_required uncertain
saved

True when a group record is persisted for this host (including the install-time hardware record). False when projected; status, remedy and approval_preview are unchanged. Absent from older servers: treat as true.

boolean
fresh
required

Whether the evidence still matches the current agent and prerequisites.

boolean
observed_at
string | null format: date-time
remedy
required

Actionable reason when pending, failed, upgrade required or uncertain; baseline_unavailable names missing current-connection deployment evidence.

string | null
next_retry_at
string | null format: date-time
attempt_id
string | null format: uuid
approval_preview
required
One of:
object
available
required

False for unavailable conditions including a live disruptive grant/attempt, unresolved disruptive admission hold, incomplete authenticated current-connection journal reconciliation or active group snapshot, missing current review-token row, or missing current-connection Automatic hardware evidence. Every condition is bound by the prerequisite digest or rotates the review ID when availability opens. Identical replay of an existing grant uses its original request and may still return 202.

boolean
revision
required

Canonical nonnegative decimal string; no sign, whitespace or leading zeros.

string
/^(0|[1-9][0-9]*)$/
content_sha256
required

Lowercase SHA-256 hex digest of canonical content.

string
/^[0-9a-f]{64}$/
resolved
required

Exact resolved values proposed for this group.

object
key
additional properties
any
prerequisites_sha256
required

Lowercase SHA-256 hex digest of canonical content.

string
/^[0-9a-f]{64}$/
prerequisites
required
Array<object>
object
kind
required
string
/^[^\x00\n]+$/
id
required
string
/^[^\x00\n]+$/
approval_boot_incarnation
required

Persisted token for the current process boot.

string format: uuid
approval_review_id
required

Present even when unavailable but grants nothing then. Stable during a live waiting/offered grant; all restart-group IDs on the host rotate when an approval exits approved or offered for cancel_pending or a terminal state or exits cancel_pending for a terminal state

string format: uuid
remedy
required
string | null
image_preparation
required

Independent evidence status. Preparation, configuration application and readiness do not imply one another.

object
status
required

Open evidence state; consumers retain unknown values.

string
observed_at
required
string | null format: date-time
remedy
required

For waiting, names current sessions (including local-only console), untracked managed sessions, preparation or unknown-inventory blockers; an empty observation never proves application.

string | null
detail
object
key
additional properties
any
readiness
required

Independent evidence status. Preparation, configuration application and readiness do not imply one another.

object
status
required

Open evidence state; consumers retain unknown values.

string
observed_at
required
string | null format: date-time
remedy
required

For waiting, names current sessions (including local-only console), untracked managed sessions, preparation or unknown-inventory blockers; an empty observation never proves application.

string | null
detail
object
key
additional properties
any
Example
{
"choices": {
"additionalProperty": {
"source": "automatic"
}
},
"resolved": {
"additionalProperty": {
"source": "automatic"
}
},
"groups": {
"additionalProperty": {
"scope": "next_session",
"status": "pending"
}
}
}

Validation_failed or unsupported_source.

Media typeapplication/json
object
error
required
object
code
required

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.

string
message
required
string
live_sessions

Present on restart_required.

integer
session_id

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.

string format: uuid
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.

Array<object>
object
id
required
string format: uuid
name
required
string
reason

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.

string
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.

Media typeapplication/json
object
error
required
object
code
required

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.

string
message
required
string
live_sessions

Present on restart_required.

integer
session_id

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.

string format: uuid
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.

Array<object>
object
id
required
string format: uuid
name
required
string
reason

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.

string
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).

Media typeapplication/json
object
error
required
object
code
required

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.

string
message
required
string
live_sessions

Present on restart_required.

integer
session_id

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.

string format: uuid
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.

Array<object>
object
id
required
string format: uuid
name
required
string
reason

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.

string
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.

Media typeapplication/json
object
error
required
object
code
required

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.

string
message
required
string
live_sessions

Present on restart_required.

integer
session_id

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.

string format: uuid
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.

Array<object>
object
id
required
string format: uuid
name
required
string
reason

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.

string
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"
}
}

Stale_revision with current view and changed keys, or upgrade_required with no write.

Media typeapplication/json
Any of:
object
error
required
object
code
required

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.

string
message
required
string
live_sessions

Present on restart_required.

integer
session_id

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.

string format: uuid
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.

Array<object>
object
id
required
string format: uuid
name
required
string
reason

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.

string
current
required
object
revision
required

Canonical nonnegative decimal string; no sign, whitespace or leading zeros.

string
/^(0|[1-9][0-9]*)$/
choices
required
object
key
additional properties
object
source
required
string
Allowed values: automatic deployment explicit
value

Required for explicit; forbidden for automatic or deployment. Validated against the frozen hostcfg catalog, including cross-key groups.

resolved
required
object
key
additional properties
object
value
required

Resolved JSON value for this setting.

source
required
string
Allowed values: automatic deployment explicit
observed_at
required

Null when no reliable resolution evidence exists.

string | null format: date-time
evidence_id
string | null
groups
required
object
key
additional properties
object
desired_revision
required

Canonical nonnegative decimal string; no sign, whitespace or leading zeros.

string
/^(0|[1-9][0-9]*)$/
applied_revision
required
One of:

Canonical nonnegative decimal string; no sign, whitespace or leading zeros.

string
/^(0|[1-9][0-9]*)$/
desired_digest
required
One of:

Lowercase SHA-256 hex digest of canonical content.

string
/^[0-9a-f]{64}$/
applied_digest
One of:

Lowercase SHA-256 hex digest of canonical content.

string
/^[0-9a-f]{64}$/
scope
required
string
Allowed values: next_session restart
status
required
string
Allowed values: pending applied failed upgrade_required uncertain
saved

True when a group record is persisted for this host (including the install-time hardware record). False when projected; status, remedy and approval_preview are unchanged. Absent from older servers: treat as true.

boolean
fresh
required

Whether the evidence still matches the current agent and prerequisites.

boolean
observed_at
string | null format: date-time
remedy
required

Actionable reason when pending, failed, upgrade required or uncertain; baseline_unavailable names missing current-connection deployment evidence.

string | null
next_retry_at
string | null format: date-time
attempt_id
string | null format: uuid
approval_preview
required
One of:
object
available
required

False for unavailable conditions including a live disruptive grant/attempt, unresolved disruptive admission hold, incomplete authenticated current-connection journal reconciliation or active group snapshot, missing current review-token row, or missing current-connection Automatic hardware evidence. Every condition is bound by the prerequisite digest or rotates the review ID when availability opens. Identical replay of an existing grant uses its original request and may still return 202.

boolean
revision
required

Canonical nonnegative decimal string; no sign, whitespace or leading zeros.

string
/^(0|[1-9][0-9]*)$/
content_sha256
required

Lowercase SHA-256 hex digest of canonical content.

string
/^[0-9a-f]{64}$/
resolved
required

Exact resolved values proposed for this group.

object
key
additional properties
any
prerequisites_sha256
required

Lowercase SHA-256 hex digest of canonical content.

string
/^[0-9a-f]{64}$/
prerequisites
required
Array<object>
object
kind
required
string
/^[^\x00\n]+$/
id
required
string
/^[^\x00\n]+$/
approval_boot_incarnation
required

Persisted token for the current process boot.

string format: uuid
approval_review_id
required

Present even when unavailable but grants nothing then. Stable during a live waiting/offered grant; all restart-group IDs on the host rotate when an approval exits approved or offered for cancel_pending or a terminal state or exits cancel_pending for a terminal state

string format: uuid
remedy
required
string | null
image_preparation
required

Independent evidence status. Preparation, configuration application and readiness do not imply one another.

object
status
required

Open evidence state; consumers retain unknown values.

string
observed_at
required
string | null format: date-time
remedy
required

For waiting, names current sessions (including local-only console), untracked managed sessions, preparation or unknown-inventory blockers; an empty observation never proves application.

string | null
detail
object
key
additional properties
any
readiness
required

Independent evidence status. Preparation, configuration application and readiness do not imply one another.

object
status
required

Open evidence state; consumers retain unknown values.

string
observed_at
required
string | null format: date-time
remedy
required

For waiting, names current sessions (including local-only console), untracked managed sessions, preparation or unknown-inventory blockers; an empty observation never proves application.

string | null
detail
object
key
additional properties
any
changed_keys
required

Keys changed since expected_revision.

Array<string>
Example
{
"current": {
"choices": {
"additionalProperty": {
"source": "automatic"
}
},
"resolved": {
"additionalProperty": {
"source": "automatic"
}
},
"groups": {
"additionalProperty": {
"scope": "next_session",
"status": "pending"
}
}
}
}