Skip to content

Read per-session telemetry.

GET
/v1/admin/sessions/{id}/metrics
curl --request GET \
--url http://localhost:8080/v1/admin/sessions/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/metrics \
--header 'Authorization: Bearer <token>'
id
required
string format: uuid

OK.

Media typeapplication/json
object
items
required
Array<object>

One row from GET /v1/admin/sessions/{id}/metrics.

object
source
required
string
Allowed values: agent browser
ts_unix_ms
required
integer
metrics
required
One of:

Metrics from the node-agent encode pipeline.

object
fps
number
bitrate_kbps
integer
encode_ms
number
frames_encoded
integer
frames_dropped
integer
encode_ms_p50
number
encode_ms_p95
number
encode_ms_max
number
overlay_frames
integer
abr_floor_kbps

The governor’s live floor, sent ONLY when the ladder has moved it off the launch floor. Absent means ‘the floor the session launched with’, never ‘unknown’. Uncurated: readable via GET …/metrics, not in the diagnostic lens.

number
abr_mode

The ABR mode this session is actually running (‘smooth’ | ‘protective’ | ‘off’). Reported rather than inferred: deriving it from setpoint presence was wrong for ‘off’, where rtpgccbwe stays attached. Uncurated (a label, not a series).

string
abr_setpoint_kbps

The in-session ABR governor’s CURRENT CBR setpoint at drain time — a level, not a window average. Absent when ABR is disarmed.

number
adaptation_state

The agent’s own per-window bottleneck label (‘healthy’ | ‘network_congested’ | ‘encoder_saturated’ | ‘unknown’). SIGNAL-ONLY — it changes no ABR behaviour, and it can never say ‘client_presentation_limited’ because the agent never sees browser data (invariant #1).

string
compositor_fps

Frames emitted by waylanddisplaysrc per second, BEFORE caps normalisation and interpipe. The first stage where a rate can differ from what the app painted.

number
compositor_pts_delta_p50_ms

P50 spacing of consecutive compositor buffer PTS. NOMINAL, NOT REALIZED: PTS come off the shared pipeline clock at the negotiated rate, so this sits at a flat ~16.668 ms at 60 fps however badly the wall-clock cadence jitters. For realized emit spacing read probe_compositor_frame_interval_p95_ms.

number
compositor_pts_delta_p95_ms

P95 of the same NOMINAL PTS spacing. Same trap as the p50: a healthy-looking 16.7 here is not evidence the compositor emitted on time.

number
external_owner

Who owns the external size — ‘auto’ (the ladder) or ‘pinned’ (a user/admin PATCH). Rides the size echo, so it is present only while the size is off the launch size.

string
external_resize_supported

Whether this session’s encode path can change the external resolution live. Always sent by a capable agent; ABSENT MEANS UNKNOWN, not false.

boolean
gcc_estimate_kbps

The raw rtpgccbwe bandwidth estimate BEFORE the governor’s EWMA / deadband / step logic. The delta against abr_setpoint_kbps is the governor’s own smoothing contribution.

number
interpipe_queue_drops

Leaky-queue overruns in the window. Host-side loss BEFORE the encoder; distinct from frames_dropped (encoder) and from the client’s frames_dropped (presentation).

integer
interpipe_queue_dwell_p50_ms

P50 time a buffer spent between the interpipe sink and src. Omitted when nothing traversed.

number
interpipe_queue_dwell_p95_ms

P95 interpipe dwell — where a queue that is filling shows up before drops do.

number
interpipe_queue_level_max

Deepest the interpipe queue got, in buffers, since the previous drain reset it. A max, so one burst sets it for the whole window — which is the point.

integer
ladder_fps

The encoded frame rate the fps rung currently asks for. Present only BELOW the launch rate; only a 120 fps session can ever carry it.

integer
ladder_res_rung

The ladder’s current external-resolution rung index (0 = launch). Present only when non-zero.

integer
ladder_speed_bias

The ABR ladder’s current encoder speed-bias rung. Present only when non-zero — absent is ‘baseline quality posture’, never ‘unknown’.

integer
probe_capture_to_enc_in_p50_ms

Host-stage latency probe: p50 of compositor src to encoder sink — the pre-encode transit the interpipe hides. Present ONLY when QUASAR_LATENCY_PROBE is on AND that stage sampled this window — absent means not measured, never 0. Observability only: nothing in the control plane may key on it.

number
probe_capture_to_enc_in_p95_ms

Host-stage latency probe: p95 of compositor src to encoder sink — the pre-encode transit the interpipe hides. Present ONLY when QUASAR_LATENCY_PROBE is on AND that stage sampled this window — absent means not measured, never 0. Observability only: nothing in the control plane may key on it.

number
probe_compositor_frame_interval_p95_ms

P95 REALIZED wall-clock spacing of compositor emits — the honest counterpart to the nominal compositor_pts_delta_p95_ms. Probe-gated.

number
probe_enc_out_to_send_p50_ms

Host-stage latency probe: p50 of encoder src to the pre-SRTP seam, FIFO-paired and guarded at one frame period so a persistent off-by-one cannot inflate every sample. Present ONLY when QUASAR_LATENCY_PROBE is on AND that stage sampled this window — absent means not measured, never 0. Observability only: nothing in the control plane may key on it.

number
probe_enc_out_to_send_p95_ms

Host-stage latency probe: p95 of encoder src to the pre-SRTP seam, FIFO-paired and guarded at one frame period so a persistent off-by-one cannot inflate every sample. Present ONLY when QUASAR_LATENCY_PROBE is on AND that stage sampled this window — absent means not measured, never 0. Observability only: nothing in the control plane may key on it.

number
probe_pay_to_send_p50_ms

Host-stage latency probe: p50 of payloader to the pre-SRTP seam, keyed on the RTP timestamp rather than on order, so unlike enc_out_to_send it cannot desync and keeps its tail. Present ONLY when QUASAR_LATENCY_PROBE is on AND that stage sampled this window — absent means not measured, never 0. Observability only: nothing in the control plane may key on it.

number
probe_pay_to_send_p95_ms

Host-stage latency probe: p95 of payloader to the pre-SRTP seam, keyed on the RTP timestamp rather than on order, so unlike enc_out_to_send it cannot desync and keeps its tail. Present ONLY when QUASAR_LATENCY_PROBE is on AND that stage sampled this window — absent means not measured, never 0. Observability only: nothing in the control plane may key on it.

number
probe_pts_to_emit_p50_ms

Host-stage latency probe: p50 of the compositor’s own PTS-to-emit hold, from Element::current_running_time. Present ONLY when QUASAR_LATENCY_PROBE is on AND that stage sampled this window — absent means not measured, never 0. Observability only: nothing in the control plane may key on it.

number
probe_pts_to_emit_p95_ms

Host-stage latency probe: p95 of the compositor’s own PTS-to-emit hold, from Element::current_running_time. Present ONLY when QUASAR_LATENCY_PROBE is on AND that stage sampled this window — absent means not measured, never 0. Observability only: nothing in the control plane may key on it.

number
probe_pts_unmatched

Encoder-input PTS that never matched a compositor-emit ring entry — the correlation key stopped surviving to the encoder. Probe-gated.

integer
probe_send_desyncs

Encoder-src entries dropped from the S3 pairing for exceeding one frame period of age — the count that says enc_out_to_send’s tail is truncated rather than clean. Probe-gated.

integer
render_height

The compositor’s CURRENT app-facing wl_output logical height; see render_width.

integer
render_width

The compositor’s CURRENT app-facing wl_output logical width. Present only when it differs from the session’s pinned stream size. With render_height and ui_scale this is the ONLY authoritative readback of live render resolution — the value lives nowhere else.

integer
rtp_bitrate_kbps

RTP packet bitrate before WebRTC transport overhead (SRTP, RTX, FEC). Always a little above bitrate_kbps; a large gap is retransmission.

number
rtp_fps

RTP access units completed per second, counted on marker packets — the last host-side rate before the wire.

number
source_fps

New buffers committed by the MAPPED FULLSCREEN app top-level per second. Cursor, popup, subsurface and configure-only commits are excluded, so this is the app’s own paint rate, not Wayland traffic.

number
stream_height

The session’s CURRENT external (encoded) height; see stream_width.

integer
stream_width

The session’s CURRENT external (encoded) width, read back from the encode graph’s NEGOTIATED caps — not from the last request. Present only when != the launch size, so absent means ‘at launch size’, never ‘unknown’.

integer
ui_scale

The compositor’s CURRENT fractional-scale preferred_scale. Present only when != 1.0.

number
next_cursor
required
string | null
Example
{
"items": [
{
"source": "agent"
}
]
}

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

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