Skip to content

Newest-first administrative activity history.

GET
/v1/admin/activity
curl --request GET \
--url 'http://localhost:8080/v1/admin/activity?limit=50' \
--header 'Authorization: Bearer <token>'
limit
integer
default: 50 >= 1 <= 100
cursor
integer
>= 1
action
string
<= 128 characters

PREFIX match on action, not a substring or a regex, so user. selects the whole user.* family and user.deleted selects one action. Dotted actions are namespaced by design; prefix is the operator’s natural query over them.

actor_user_id
string format: uuid

Exact match on actor_user_id. A malformed uuid is 400 validation_failed. Rows with a null actor (server-originated events such as session.failed) are never returned by this filter.

target_type
string
<= 64 characters

Exact match on target_type (user, host, session, app, image, invite, instance, …).

since
string format: date-time

Lower bound, inclusive, on created_at (RFC 3339). There is deliberately no until: this feed is read newest-first from now backwards, and cursor already bounds the other end. An unparseable value is 400 validation_failed.

q
string
<= 128 characters

Case-insensitive substring over action, target_id and the joined actor username - the three things an operator can actually read off the page. Deliberately NOT over details: that column holds operator-supplied free text, and a substring search across it would turn a note into a way to find unrelated rows. Escapes % and _ so a literal underscore matches a literal underscore.

OK.

Media typeapplication/json
object
items
required
Array<object>
object
id
required
integer format: int64
actor_user_id
required
string | null format: uuid
action
required
string
target_type
required
string
target_id
required
string | null
details
required
object
key
additional properties
any
created_at
required
string format: date-time
actor_username
required

users.username for actor_user_id, resolved by a LEFT JOIN at read time and never stored - the log is append-only, so a later rename must show the current name, not the one at the time. Null when the actor row is gone (the audit table keeps no FK on purpose: a deleted admin must not take their history with them) and null when the event had no actor at all.

string | null
severity
required

DERIVED SERVER-SIDE from action, never stored and never client-supplied, so one rule governs every consumer: *.failed and any action containing error are err; *.deleted, *.delete, *.revoked, *.disabled, plus host.drain, session.stop and storage.home.tombstone, are warn; everything else is info. A new action needs no schema change - it lands as info unless it matches a rule.

string
Allowed values: info warn err
next_cursor
required
integer | null format: int64
Example
{
"items": [
{
"severity": "info"
}
]
}

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

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

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

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. 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
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"
}
]
}
}