---
title: "Query Link Agents"
method: GET
path: "/link/agents"
tags: ["LinkAgent"]
---

# Query Link Agents

`GET /link/agents`

Query for Link Agents by workspace ID

## Query parameters

- `workspace_id` string, required
- `label` string
- `max_heartbeat_age_sec` integer
- `limit` integer
- `cursor` string

## Response `200`

Results of the query

- LinkAgentQueryResult
  - `link_agents` LinkAgent[] — All Link Agents that matched the query
    - `id` string — UUID for this Link Agent instance
    - `created_time` integer — Time at which the Link Agent was created
    - `created_by_id` string — The principal that registered this Link Agent — normally the API key the agent runs under. Server-set from the authenticated caller; supplying it is ignored. Activity feed attribution reads this, so an agent without it is reported as having been registered by mabl itself.
    - `last_updated_time` integer — Time at which the Link Agent record last changed
    - `last_updated_by_id` string — The principal behind the most recent change to this Link Agent — the API key for a status change reported by the agent, or the user who started or ended a maintenance window. Server-set from the authenticated caller; supplying it is ignored.
    - `deleted_time` integer — Time at which the Link Agent was stopped or removed
    - `last_heartbeat_time` integer — Time at which the Link Agent last sent a heartbeat
    - `version` string — The version of the Link Agent software that this instance is running
    - `distribution` 'java' | 'mabl_cli' — Which Link Agent distribution an instance is — the Java Link Agent (`java`) or the mabl-cli Link Agent (`mabl_cli`).
    - `update_available` boolean — Whether a newer Link Agent release than this agent's version is available for download. Computed for the Java distribution only — the mabl-cli distribution self-updates via its own channels and its version is not comparable to Java releases. Server-computed on read; absent when it cannot be determined (the mabl-cli distribution, an unknown or unparseable agent version, or the release inventory being unavailable).
    - `latest_available_version` string — The newest released Link Agent version, present whenever update_available is computed
    - `label` string — The user-provided label for this Link Agent instance
    - `point_of_contact` string — Optional point of contact (a name or email address) for this Link Agent, supplied by the operator via config file or command-line at startup. Surfaced so support can tell who to contact about a given Agent. Free-form — not validated as an email.
    - `no_ssl_verify` boolean — Flag indicating whether the agent was started with SSL verification disabled
    - `tunnel_port` integer — DEPRECATED - Not used as of Link 2.0
    - `hostname` string — The Link Agent instance's local hostname
    - `machine_identity` string — Opaque persisted per-machine identity — a mabl-minted UUID stored in the standard OS-specific mabl config directory — required when announcing a personal (`user`-owned) tunnel. The server derives the tunnel's label from it (SHA-256 of `machine_identity ":" link_infrastructure_key`, lowercase hex, truncated to the 24-char label limit), so one machine keeps one stable tunnel identity per cluster across restarts, reboots, and hostname changes. Not used for workspace- or company-owned agents.
    - `link_infrastructure_key` string — Personal (`user`-owned) announces only. On request: the explicit placement choice when the user's workspace associations resolve to more than one link infrastructure — the ambiguous announce fails with an error naming the candidate keys, and the shared cluster's key is always a valid choice. Omit when the associations are unambiguous. On response: the resolved placement recorded for this agent's tunnel. Ignored for workspace- and company-owned agents, whose placement derives live from the owning company.
    - `address` string — The Link Agent instance's local IP address
    - `java_version` string — Value of the 'java.version' Java system property
    - `java_vm_name` string — Value of the 'java.vm.name' Java system property
    - `operating_system_architecture` string — Value of the 'os.arch' Java system property
    - `operating_system_name` string — Value of the 'os.name' Java system property
    - `operating_system_version` string — Value of the 'os.version' Java system property
    - `cpu_cores` integer — Number of CPU cores available to the Agent host — the headroom denominator for CPU utilization.
    - `total_memory_bytes` integer — Total physical memory on the Agent host, in bytes — the headroom denominator for memory utilization.
    - `tunnels_on_host` integer — How many tunnels this Agent process supervises, as the Agent itself counts them. Every mablnet tunnel runs its own worker process, so this is the multiplier on the host's memory requirement — the api pairs it with `total_memory_bytes` to flag an undersized host. Each of a multi-tunnel process's Agent records reports the same value. Reported by the Agent rather than derived from records sharing a hostname, which containers and cloned VM images make unreliable. Absent from Agents predating the field.
    - `os_settings` LinkAgentOsSettings — Static host OS resource limits and network sysctls the Agent reads once at startup (from `/proc` on Linux) and reports on announce. These bind a connection-heavy tunnel well before the mablnet protocol's own stream ceiling: every proxied connection costs a file descriptor and an outbound ephemeral port, so a low open-file limit or a narrow ephemeral-port range caps how many concurrent connections the Agent — and its worker subprocess, which inherits the Agent's rlimits — can carry. Surfaced so operators can audit whether a host is tuned for the Agent's load. Fields are individually omitted when unreadable (e.g. on a non-Linux host), so the object may be sparse or absent.
      - `open_file_limit` integer — Soft `RLIMIT_NOFILE` — the max open file descriptors the process may hold and the limit that actually binds at runtime. Each proxied connection consumes one. `-1` means unlimited. The common Linux default (1024) is low for a busy tunnel.
      - `open_file_limit_hard` integer — Hard `RLIMIT_NOFILE` — the ceiling the soft limit can be raised to without privilege. `-1` means unlimited. When this is also low, raising the soft limit needs a `limits.conf` / systemd change rather than a plain `ulimit -n`.
      - `max_user_processes` integer — Soft `RLIMIT_NPROC` — the max processes/threads for the Agent's user. `-1` means unlimited. JVM and worker threads (Netty event loops, etc.) count against it.
      - `ephemeral_port_range_low` integer — Low end of `net.ipv4.ip_local_port_range` — the first ephemeral port the kernel hands out to outbound connections. With `ephemeral_port_range_high` it bounds how many simultaneous outbound connections (per remote tuple) the host can open.
      - `ephemeral_port_range_high` integer — High end of `net.ipv4.ip_local_port_range` (inclusive).
      - `socket_recv_buffer_max` integer — `net.core.rmem_max` — the max receive socket buffer (bytes) an application may request. Caps carrier throughput on high-latency links when set below what the carrier asks for (the default of ~208 KB is a common bottleneck).
      - `socket_send_buffer_max` integer — `net.core.wmem_max` — the max send socket buffer (bytes) an application may request.
      - `udp_recv_buffer_achieved` integer — Actual `SO_RCVBUF` (bytes) a UDP socket obtains when it requests the QUIC carrier's target buffer — the post-clamp value `getsockopt` returns, which the kernel caps near `net.core.rmem_max` (and doubles for bookkeeping on Linux). The Agent measures this with a throwaway probe socket, so it is present on every OS (unlike `socket_recv_buffer_max`, which is Linux `/proc`-only). The real signal for whether this host can honor the QUIC carrier's receive flow-control window.
      - `udp_send_buffer_achieved` integer — Actual `SO_SNDBUF` (bytes) a UDP socket obtains for the carrier's target request — the send-side counterpart of `udp_recv_buffer_achieved`, capped near `net.core.wmem_max`.
      - `udp_buffer_clamped` boolean — True when the achieved UDP socket buffer fell materially short of the QUIC carrier's request — i.e. the kernel clamped it (the host needs `net.core.rmem_max` / `wmem_max` raised, or, in a container, the host / WSL2 VM tuned). A clamped buffer throttles QUIC throughput on higher-latency links, so this is the health signal for deciding whether to default this host to the QUIC carrier or fall back to WSS.
      - `conntrack_max` integer — `net.netfilter.nf_conntrack_max` — the connection-tracking table size limit, the ceiling for the dynamic `agent_state.host.conntrack_count`. Present only on Linux hosts with the conntrack module loaded (a stateful firewall/NAT); absent otherwise.
    - `capacity` LinkAgentCapacity — Server-derived capacity insights, recomputed on every read (never persisted) from the heartbeat `agent_state.host` utilization peaks, the static headroom denominators (`cpu_cores`, `total_memory_bytes`, `os_settings`), and the `connections_rejected` saturation counter. Answers "how close is this Agent to its limits, and is its host tuned?" for the capacity-insights UI. Present once the Agent has announced its memory total — enough for the `supported_tunnels` projection — with the utilization dimensions filling in from its first heartbeat carrying host stats.
      - `health` 'healthy' | 'warning' | 'critical' | 'unknown' — Capacity health band, used both for the Agent overall and per resource dimension. `healthy` = comfortable headroom; `warning` = approaching a limit (tune or scale soon); `critical` = at/over a limit, or the Agent has already rejected connections for want of capacity; `unknown` = not enough data yet (no heartbeat carrying host stats). As an OS-warning severity only `warning` / `critical` are used.
      - `binding_constraint` 'cpu' | 'memory' | 'file_descriptors' | 'ephemeral_ports' | 'conntrack' | 'disk' | 'network' — A resource dimension whose live utilization the api compares against its ceiling.
      - `dimensions` LinkAgentCapacityDimension[] — Per-resource utilization-vs-ceiling, one entry per computable dimension.
        - `resource` 'cpu' | 'memory' | 'file_descriptors' | 'ephemeral_ports' | 'conntrack' | 'disk' | 'network' — A resource dimension whose live utilization the api compares against its ceiling.
        - `utilization_pct` number, double — Peak usage as a percentage (0–100) of the ceiling for this dimension.
        - `status` 'healthy' | 'warning' | 'critical' | 'unknown' — Capacity health band, used both for the Agent overall and per resource dimension. `healthy` = comfortable headroom; `warning` = approaching a limit (tune or scale soon); `critical` = at/over a limit, or the Agent has already rejected connections for want of capacity; `unknown` = not enough data yet (no heartbeat carrying host stats). As an OS-warning severity only `warning` / `critical` are used.
        - `used` integer — Peak raw usage in the dimension's native unit (memory bytes, file-descriptor count, ephemeral-port count). Omitted for `cpu`, whose utilization is already a percentage.
        - `limit` integer — Ceiling in the same unit as `used`. Omitted for `cpu`, and when the limit is unbounded (e.g. an unlimited file-descriptor rlimit).
      - `os_warnings` LinkAgentOsWarning[] — Conditions on the Agent's host or network the customer should act on — OS settings below recommended values, the host's memory against the tunnels it runs, and mabl endpoints the Agent cannot reach. Empty when the host is well-tuned, adequately sized, and reaching mabl.
        - `setting` string — The flagged property, and the stable key a client localizes on — one of `open_file_limit`, `ephemeral_port_range`, `socket_buffer_limits`, `host_memory`, `link_endpoint_unreachable`. A client that does not recognise a value should skip the advisory: there is no prose here to fall back on.
        - `severity` 'healthy' | 'warning' | 'critical' | 'unknown' — Capacity health band, used both for the Agent overall and per resource dimension. `healthy` = comfortable headroom; `warning` = approaching a limit (tune or scale soon); `critical` = at/over a limit, or the Agent has already rejected connections for want of capacity; `unknown` = not enough data yet (no heartbeat carrying host stats). As an OS-warning severity only `warning` / `critical` are used.
        - `current` integer — The Agent's current value for the setting. Absent for an advisory that is not about a numeric setting at all (`link_endpoint_unreachable`).
        - `recommended` integer — The minimum value the api recommends for a busy tunnel. Absent where `current` is.
        - `params` object — Everything this advisory's copy needs that `current` / `recommended` do not already carry. Keys are stable per `setting`: * `open_file_limit` — none; `current` and `recommended` are the whole story. * `ephemeral_port_range` — `low`, `high`: the range endpoints (`current` is its size). * `socket_buffer_limits` — `variant`: `sysctls_known` when the host's `net.core.rmem_max` / `wmem_max` were readable and the advice can name them, else `sysctls_unknown`. The two texts differ in structure rather than in values, so a client selects between them (i18next: pass it as `context`) rather than interpolating. Both reproduce from `recommended`; nothing about the remediation needs prose from the api. * `host_memory` — `tunnels`: how many tunnels the host runs, the multiplier behind `recommended`. * `link_endpoint_unreachable` — `quic_endpoint` / `wss_endpoint`: the `host:port` the Agent could not reach, present only for an endpoint it actually attempted (a mabl-bound HTTP proxy suppresses the QUIC carrier entirely, and naming an endpoint the Agent never dialled would send the customer after the wrong firewall rule). The Agent's own account of the failure is on `connection_status[*].last_error`. Plus `variant`: `fallback_available` when the Agent is reporting a working legacy `wss` connection to carry traffic over meanwhile, `no_fallback` when it is not — either because the tunnel is mablnet-only or because the Agent cannot reach anything at all. The two consequences are different sentences, so a client selects between them (i18next: `context`) rather than interpolating. The transport belongs to the key, not a param: `quic_endpoint` is UDP, `wss_endpoint` is TCP.
      - `supported_tunnels` integer — How many tunnels this host's memory supports — a projection, not a limit the api enforces, and not a health signal: it is informational even when the Agent runs fewer. Pair it with `tunnels_on_host` to render "running 4 of 14". Derived from `total_memory_bytes` alone, allowing for the Agent process and for the extra worker an update runs alongside the others, since each tunnel runs its own worker process. It therefore assumes the host is dedicated to the Agent — a host also running a database supports fewer, which the `memory` dimension's live utilization is what reveals. Absent when the Agent reported no memory total, and `0` for a host too small for even one tunnel. Capped at 64 however much memory the host has. Memory is the only term measured, and on a large host it is not the binding one — core count, the file-descriptor limit and the ephemeral-port range bite first, and turning those into a tunnel count needs data we do not have. Those limits are reported separately instead: as `open_file_limit` and `ephemeral_port_range` advisories, and as the live `cpu` dimension.
    - `protocol` 'ssl' | 'https' | 'wss' | 'mablnet_wss' | 'mablnet_quic' — Protocol the Link Agent uses to reach the mabl side. Legacy values (pre-mablnet): - `wss`: WebSocket over TLS to a Link Server (the only one of the three that shipped — `ssl` and `https` are historical placeholders). - `ssl`, `https`: never implemented; kept in the enum for backwards compatibility with serialized records that captured them. New values (mablnet): - `mablnet_wss`: WSS carrier to a mablnet Router (the production default for mablnet-capable Agents). Goes through the Application LB on :443. - `mablnet_quic`: UDP/QUIC carrier. Reserved for future use; not externally exposed in v1 (the Router's UDP carrier is loopback-only for the in-process operator-tenant Host). Defined here so the enum is stable when UDP carriers ship.
    - `protocols` LinkAgentProtocolEnum[] — The set of protocols this Link Agent is currently attempting to keep connections open for. Successor to the singular `protocol` field. A dual-stack Agent during the mablnet migration reports both `wss` (legacy Link Tunnel) and one of the `mablnet_*` values; mabl ops watches this set to determine which accounts are migration-eligible.
    - `capabilities` LinkAgentCapabilityEnum[] — Optional behaviors this Link Agent supports. Reported by the Agent at startup. Operator endpoints that depend on Agent-side handling (`POST /link/agents/{id}/maintenance`, etc.) reject requests when the relevant capability is missing — older Agents that predate the capability simply omit it.
    - `is_connected` boolean — DEPRECATED: legacy single-protocol connection state. Agents reporting `protocols` populate `endpoints` with per-protocol connection state; this field stays populated as `true` if any of the per-protocol states are connected.
    - `current_link_server_id` string — DEPRECATED: legacy single-Link-Server binding. Mablnet Agents are not bound to a single Server — they multi-home across Routers. Stays populated for legacy Agents.
    - `current_link_server_endpoint` string — DEPRECATED: legacy single-endpoint field. Use `endpoints[wss].address` for the same value; the legacy field stays populated for pre-mablnet Agents that don't yet understand the `endpoints` map.
    - `endpoints` object — Per-protocol endpoint the Link Agent should connect to, keyed by protocol name (matches `LinkAgentProtocolEnum`). Legacy Link Tunnel Agents only see (and need) the `wss` entry, which mirrors `current_link_server_endpoint`. Mablnet-capable Agents additionally see `mablnet_wss` and (when UDP/QUIC is exposed externally) `mablnet_quic`, pointing at the Application LB FQDN for this environment. api-set and read-only from the client perspective: the value flows api → Agent. Per-protocol connection health flowing the other direction (Agent → api) is reported via `connection_status`. Computed on every read from the LinkLabel + workspace migration flags + deployment config (`com.mabl.mablnet.domain` + the owner's link infrastructure key); NOT persisted on the LinkAgent itself. A feature-flag flip, FQDN change, or LinkLabel-level update propagates to running Agents on their next poll (≈ 10 s), with no agent restart and no backfill cron. Downstream consumers that need the same view (BigQuery, Pub/Sub) should read via the api rather than the raw DAO entity.
    - `connection_status` object — Per-protocol connection health, keyed by protocol name (same keys as `endpoints`). Reported by the Agent on heartbeat and merged in.
    - `mablnet_tenant_id` integer — Denormalised view of `LinkLabel.mablnet_tenant_id` for this Agent's label. Computed on every read alongside `endpoints` (see that field's description for the full server-set-fields contract); NOT persisted on the LinkAgent. Tenant ids themselves are immutable for a LinkLabel's lifetime, so the value is stable across reads in practice.
    - `mablnet_tenant_id_hex` string — Zero-padded 8-character lowercase hex form of `mablnet_tenant_id`, e.g. `bee85e2b`. Provided for log/grep/observability ergonomics; the numeric and hex forms always agree (both computed together). Same lifecycle as `mablnet_tenant_id` — computed on every read, not persisted.
    - `status` 'initializing' | 'updating' | 'announcing' | 'locating' | 'connecting' | 'ready' | 'draining' | 'drained' | 'terminated' — The current status of the Link Agent
    - `maintenance` LinkAgentMaintenance — Operator-requested maintenance state for a Link Agent. While the Agent's status is `draining` or `drained`, mabl-side routing treats it as ineligible for new traffic (mablnet drains its carriers), but in-flight connections continue to be serviced until they finish or the drain timeout expires. The object persists as a record of the most-recent maintenance window after the Agent returns to service.
      - `drain_started` integer — When the Agent was placed into maintenance and began draining (epoch millis).
      - `drain_completed` integer — When the Agent finished draining — either all in-flight connections completed or `drain_timeout_seconds` elapsed (status `drained`). Set server-side when the Agent first reports it has drained; absent while the status is still `draining`. When present, the Agent is safe to terminate.
      - `drain_timeout_seconds` integer — Operator-supplied upper bound on how long the Agent will wait for in-flight connections to drain before transitioning to `drained`. Omitted for an unbounded wait.
    - `operator_tunables` LinkAgentOperatorTunables — Operator-tunable parameters for a running Link Agent. Used in two contexts: as the read-only resolved view (`operator_tunables`) and as the sparse overrides (`operator_tunables_overrides`). In the overrides context absent fields fall through to the next merge layer; `null` explicitly clears the override (`x-dao-apply-nulls: true` per-field). In the resolved-view context the api populates every field by merging static defaults (in `LinkLabelServiceImpl`) with label-level overrides and (on `LinkAgent`) agent-level overrides.
      - `mablnet_min_carriers` integer, nullable — Steady-state target carrier count for the mablnet Host. PATCH `null` to clear an override and fall back to the next merge layer.
      - `mablnet_max_carriers` integer, nullable — Advisory ceiling — overlap budget for in-flight carrier churn.
      - `mablnet_join_timeout` string, duration, nullable — Per-carrier-join timeout. Tighten only when investigating a misbehaving LB.
      - `mablnet_telemetry_export_interval` string, duration, nullable — Cadence at which the Agent-side SDK MeterProvider flushes to the mablnet-metrics-collector. Drop to ~`PT5S` during incidents.
      - `mablnet_carrier_recycle_interval` string, duration, nullable — Period between proactive carrier-recycle ticks: the worker retires its oldest carrier (make-before-break, so existing QUIC sessions migrate without disruption) and the LB redistributes the replacement across the Router fleet. Default `PT60M`. Set `PT0S` to disable proactive recycle (the worker still retires unhealthy carriers). PATCH `null` to clear an override.
      - `mablnet_carrier_recycle_jitter` string, duration, nullable — Random offset added to the first carrier-recycle tick, to desync the fleet so recycles don't bunch into a thundering-herd reconnect. Default `PT30M`. No effect when `mablnet_carrier_recycle_interval` is `PT0S`.
      - `mablnet_allowed_protocols` LinkAgentProtocolEnum[], nullable — The set of mablnet carrier protocols this tunnel is allowed to use (default `[mablnet_quic, mablnet_wss]`). This is the mabl-controlled *policy* — which carriers are permitted — NOT a try-order: order here is not significant. Each host selects the best available protocol from this set (and the fallback order among them) using local signals the operator can't know ahead of time — UDP socket-buffer clamp, an HTTP proxy in the path, live reachability. Set `[mablnet_wss]` to disable QUIC for a tunnel on a known UDP-blocked network, or `[mablnet_quic]` to require QUIC. Only `mablnet_*` values are valid; the worker rebuilds on change. PATCH `null` to clear an override.
      - `mablnet_carrier_connect_timeouts` object, nullable — Per-protocol carrier establishment deadline in milliseconds, keyed by `LinkAgentProtocolEnum` value (default `{mablnet_quic: 8000, mablnet_wss: 45000}`): how long the worker waits for a carrier on that protocol to JOIN before falling back to the next protocol the host tries (from `mablnet_allowed_protocols`). The deadline is intrinsic to the protocol — QUIC fast-fails to escape silently-blackholing networks; WSS is patient — and is independent of order. A protocol absent from the map uses the worker's built-in default. PATCH `null` to clear the override.
      - `mablnet_client_pool_size` integer, nullable — Multi-carrier peer-pool size for CLIENT legs connecting to this tunnel. Deliberately has NO server-side default: unset means each client auto-selects a fixed pool size from its own machine's resources (total memory, capped by cores) — the api can't know them. An explicit value pins the size for every client of the tunnel: `1` = pool off, N > 1 boots N mablnet Hosts per client — one slot identity each, each with its own carriers — and spreads connections across them, lifting the one-pinned-carrier-per-peer throughput cap for many-connection workloads (browser tests). Does not speed up a single bulk stream, and the Link Agent itself is unaffected — this rides to clients on the resolved tunnel (`client_pool_size`). Consumers treat explicit values < 1 as 1; keep N modest (2-4). PATCH `null` to restore client auto-selection.
      - `ready_heartbeat_interval` string, duration, nullable — Heartbeat cadence once the Agent reaches `ready` status. While the Agent is still bringing up the legacy / mablnet stacks (status != `ready`) it heartbeats at the hard-coded 10 s tick — fast feedback is what ops + the UI need during startup, and the startup window is short. Once `ready` the cadence drops to this value (default `PT60S`); steady-state Agents mostly only need the heartbeat to guard against sudden death (laptop suspend, network drop) and that doesn't repay 10 s × every Agent in the fleet of api-side load. The Agent reverts to 10 s if `status` ever leaves `ready`. Must stay below the api's dead-agent cleanup threshold (10 min) — `PT300S` (5 min) is a safe upper bound.
      - `agent_log_level` 'trace' | 'debug' | 'info' | 'warn' | 'error', nullable — Root logger level. Applied in-place — no Host rebuild. Deliberately has no server-side default, and should not be given one. The Agent applies this to its process-global root logger on the first announce, which happens *after* it has applied the level from its own config file / command line — so any value set here silently overrides what the customer configured locally. Left unset (the default) the local config stays authoritative; set explicitly, it means an operator chose this level for this tunnel and it wins.
    - `operator_tunables_overrides` LinkAgentOperatorTunables — Operator-tunable parameters for a running Link Agent. Used in two contexts: as the read-only resolved view (`operator_tunables`) and as the sparse overrides (`operator_tunables_overrides`). In the overrides context absent fields fall through to the next merge layer; `null` explicitly clears the override (`x-dao-apply-nulls: true` per-field). In the resolved-view context the api populates every field by merging static defaults (in `LinkLabelServiceImpl`) with label-level overrides and (on `LinkAgent`) agent-level overrides.
      - `mablnet_min_carriers` integer, nullable — Steady-state target carrier count for the mablnet Host. PATCH `null` to clear an override and fall back to the next merge layer.
      - `mablnet_max_carriers` integer, nullable — Advisory ceiling — overlap budget for in-flight carrier churn.
      - `mablnet_join_timeout` string, duration, nullable — Per-carrier-join timeout. Tighten only when investigating a misbehaving LB.
      - `mablnet_telemetry_export_interval` string, duration, nullable — Cadence at which the Agent-side SDK MeterProvider flushes to the mablnet-metrics-collector. Drop to ~`PT5S` during incidents.
      - `mablnet_carrier_recycle_interval` string, duration, nullable — Period between proactive carrier-recycle ticks: the worker retires its oldest carrier (make-before-break, so existing QUIC sessions migrate without disruption) and the LB redistributes the replacement across the Router fleet. Default `PT60M`. Set `PT0S` to disable proactive recycle (the worker still retires unhealthy carriers). PATCH `null` to clear an override.
      - `mablnet_carrier_recycle_jitter` string, duration, nullable — Random offset added to the first carrier-recycle tick, to desync the fleet so recycles don't bunch into a thundering-herd reconnect. Default `PT30M`. No effect when `mablnet_carrier_recycle_interval` is `PT0S`.
      - `mablnet_allowed_protocols` LinkAgentProtocolEnum[], nullable — The set of mablnet carrier protocols this tunnel is allowed to use (default `[mablnet_quic, mablnet_wss]`). This is the mabl-controlled *policy* — which carriers are permitted — NOT a try-order: order here is not significant. Each host selects the best available protocol from this set (and the fallback order among them) using local signals the operator can't know ahead of time — UDP socket-buffer clamp, an HTTP proxy in the path, live reachability. Set `[mablnet_wss]` to disable QUIC for a tunnel on a known UDP-blocked network, or `[mablnet_quic]` to require QUIC. Only `mablnet_*` values are valid; the worker rebuilds on change. PATCH `null` to clear an override.
      - `mablnet_carrier_connect_timeouts` object, nullable — Per-protocol carrier establishment deadline in milliseconds, keyed by `LinkAgentProtocolEnum` value (default `{mablnet_quic: 8000, mablnet_wss: 45000}`): how long the worker waits for a carrier on that protocol to JOIN before falling back to the next protocol the host tries (from `mablnet_allowed_protocols`). The deadline is intrinsic to the protocol — QUIC fast-fails to escape silently-blackholing networks; WSS is patient — and is independent of order. A protocol absent from the map uses the worker's built-in default. PATCH `null` to clear the override.
      - `mablnet_client_pool_size` integer, nullable — Multi-carrier peer-pool size for CLIENT legs connecting to this tunnel. Deliberately has NO server-side default: unset means each client auto-selects a fixed pool size from its own machine's resources (total memory, capped by cores) — the api can't know them. An explicit value pins the size for every client of the tunnel: `1` = pool off, N > 1 boots N mablnet Hosts per client — one slot identity each, each with its own carriers — and spreads connections across them, lifting the one-pinned-carrier-per-peer throughput cap for many-connection workloads (browser tests). Does not speed up a single bulk stream, and the Link Agent itself is unaffected — this rides to clients on the resolved tunnel (`client_pool_size`). Consumers treat explicit values < 1 as 1; keep N modest (2-4). PATCH `null` to restore client auto-selection.
      - `ready_heartbeat_interval` string, duration, nullable — Heartbeat cadence once the Agent reaches `ready` status. While the Agent is still bringing up the legacy / mablnet stacks (status != `ready`) it heartbeats at the hard-coded 10 s tick — fast feedback is what ops + the UI need during startup, and the startup window is short. Once `ready` the cadence drops to this value (default `PT60S`); steady-state Agents mostly only need the heartbeat to guard against sudden death (laptop suspend, network drop) and that doesn't repay 10 s × every Agent in the fleet of api-side load. The Agent reverts to 10 s if `status` ever leaves `ready`. Must stay below the api's dead-agent cleanup threshold (10 min) — `PT300S` (5 min) is a safe upper bound.
      - `agent_log_level` 'trace' | 'debug' | 'info' | 'warn' | 'error', nullable — Root logger level. Applied in-place — no Host rebuild. Deliberately has no server-side default, and should not be given one. The Agent applies this to its process-global root logger on the first announce, which happens *after* it has applied the level from its own config file / command line — so any value set here silently overrides what the customer configured locally. Left unset (the default) the local config stays authoritative; set explicitly, it means an operator chose this level for this tunnel and it wins.
    - `agent_state` LinkAgentState — Rolled-up just-in-time snapshot the Agent pushes on every heartbeat. Snapshot semantics — the api stores only the most recent value as the canonical "current state" view. Trend graphs live in Cloud Monitoring via the mablnet-metrics-collector OTel pipeline; this surface is for incident-triage ergonomics ("show me this Agent's view of the world right now").
      - `mablnet` LinkAgentMablnetState — Per-Host mablnet snapshot, populated by the Agent from MablnetClient.
        - `carriers_open` integer — Number of carriers currently in OPEN state.
        - `carriers_by_router` object — Map of Router id → carrier count. Useful for "is this Agent multi-homed?" and for the per-carrier debug actions (the operator can target a specific Router id reported here).
        - `peer_count` integer — Number of remote peer Hosts the Agent's Host currently knows about.
        - `socks_streams_active` integer — Number of inbound SOCKS5 streams currently active (handshake + bridging).
        - `socks_streams_total` integer — Total inbound SOCKS5 streams accepted since the process started.
        - `rtt_p50_ms` number, double — p50 of recent carrier RTT, in milliseconds.
        - `rtt_p99_ms` number, double — p99 of recent carrier RTT, in milliseconds.
        - `send_window_saturation_pct` number, double — Share of the last heartbeat window (0-100) during which the Agent's tunnel send path was network-bound — SOCKS sessions sitting at their in-flight write-window high-water mark, i.e. customer data queued behind available tunnel capacity. Computed by the Agent from the worker's cumulative saturation clock; clamped, so overlapping saturated sessions read as fully saturated rather than >100. Sustained high values mean the tunnel itself (not the host) is the constraint — the "add Link Agents" signal. Absent when the Agent or worker predates the signal.
        - `worker_version` string — Version of the native mablnet host worker binary the Agent is currently running. Distinct from the Agent's top-level `version` (the Agent software / distribution version): the worker self-updates on its own channel, so this tracks the running worker generation independently. Absent until the worker has started.
      - `legacy` LinkAgentLegacyState — Legacy WSS Tunnel snapshot.
        - `tunnel_clients_connected` integer — Number of `TunnelClient` instances reporting `is_connected = true`.
      - `host` LinkAgentHostState — Host + worker-process resource-utilization snapshot the Agent assembles at heartbeat time. CPU and memory are host-wide, read from the JVM's OperatingSystemMXBean (so they include the worker subprocess); the file-descriptor, ephemeral-port, and connection-rejection counts are the worker process's own, pushed to the Agent over the worker IPC. Current samples plus high-water marks since the previous heartbeat — peaks matter for "how close to its limits" because a periodic sample misses bursts. Pair with the static `cpu_cores` / `total_memory_bytes` / `os_settings` on the Agent for the headroom denominators.
        - `cpu_utilization_pct` number, double — Host CPU utilization at the most recent sample, as a percentage (0–100) averaged across all cores.
        - `peak_cpu_utilization_pct` number, double — Highest CPU-utilization percentage (0–100) observed since the previous heartbeat.
        - `memory_used_bytes` integer — Host memory in use at the most recent sample, in bytes.
        - `peak_memory_used_bytes` integer — Peak host memory in use since the previous heartbeat, in bytes.
        - `memory_available_bytes` integer — Host memory available at the most recent sample, in bytes.
        - `open_fds` integer — Open file descriptors held by the worker process at the most recent sample. Each proxied connection consumes one; compare against `os_settings.open_file_limit` for the dominant connection-ceiling headroom.
        - `peak_open_fds` integer — Peak worker open file descriptors since the previous heartbeat.
        - `ephemeral_ports_in_use` integer — Outbound connections the worker currently holds open, each consuming one ephemeral port — compare against the `os_settings.ephemeral_port_range` size. Counts active connections only; sockets lingering in TIME_WAIT (which still hold a port briefly) are not included.
        - `peak_ephemeral_ports_in_use` integer — Peak worker ephemeral-port usage since the previous heartbeat.
        - `connections_rejected` integer — Cumulative count, since the worker process started, of outbound dials the worker rejected because a host resource limit was hit (open-file-descriptor or ephemeral-port exhaustion) — not ordinary connection-refused/timeout failures. The unambiguous saturation signal: any nonzero value means the Agent has already turned customer traffic away for want of capacity.
        - `load_average` number, double — System load average (1-minute) at the most recent sample, from the JVM OperatingSystemMXBean. Compare against `cpu_cores`: a load approaching or exceeding the core count means the run queue is backed up (CPU- or IO-bound) in a way instantaneous CPU% can miss. Absent on Windows (the JVM doesn't expose it there).
        - `swap_used_bytes` integer — Swap space in use at the most recent sample, in bytes. Non-zero (and growing) swap use is a memory-pressure signal even when `memory_available_bytes` still looks healthy.
        - `swap_total_bytes` integer — Total swap space on the host, in bytes (the denominator for `swap_used_bytes`).
        - `disk_free_bytes` integer — Free space, in bytes, on the filesystem holding the Agent's working directory. A full disk breaks worker auto-updates, update staging, and logging.
        - `disk_total_bytes` integer — Total size, in bytes, of the filesystem holding the Agent's working directory.
        - `conntrack_count` integer — Netfilter connection-tracking entries currently in use at the most recent sample (Linux `nf_conntrack_count`). On a connection-heavy host behind a stateful firewall/NAT, conntrack-table exhaustion drops new connections before any FD/port limit is hit. Absent when the conntrack module isn't loaded.
        - `peak_conntrack_count` integer — Peak netfilter conntrack entries observed since the previous heartbeat.
        - `udp_rcvbuf_errors` integer — UDP datagrams the kernel dropped for lack of receive-buffer space since the previous heartbeat (Linux `/proc/net/snmp` `Udp: RcvbufErrors`). Read as a trend, not as an attribution: the kernel sums this over **every** UDP socket in the host's network namespace, so it covers the worker's QUIC carrier sockets but also everything else on the box — a browser's QUIC connections, mDNS, a local DNS resolver — and on a general-purpose host those usually dominate it. A nonzero value therefore means inbound UDP throughput is being lost somewhere on the host, not that the carrier lost anything. Correlate with `os_settings.udp_buffer_clamped` before concluding the buffers are too small: that flag is what says a requested socket buffer was actually capped, and it is the only case where raising `net.core.rmem_max` changes the outcome. Absent off Linux.
      - `recent_errors` LinkAgentRecentError[] — Fingerprint-deduped ring of the 5 most recent error categories the Agent has seen since process start (or since the last `recent_errors` rotation). Categories — not stack traces or messages — so the field is bounded and PII-safe. Full traces stay in the Agent's local log.
        - `timestamp` string, date-time — Last time the Agent saw an error matching this fingerprint.
        - `fingerprint` string — Stable category for this error (e.g. `carrier-connect-failed`, `peer-ca-fetch-401`, `mablnet-host-rebuild`). Categories, not stack traces — kept narrow so the field stays bounded across noisy upstreams.
        - `count` integer — Number of distinct errors that mapped to this fingerprint in the window.
    - `agent_configuration` LinkAgentConfiguration — A Link Agent's effective, credential-free runtime configuration. Populated by both the Java Link Agent (the full surface below) and the mabl-cli Link Agent (its narrower carrier/uplink surface); fields the reporting Agent has no value for are omitted. Never carries secrets — proxy, PAC, and tunnel credentials are represented only by the `proxy_auth_configured` boolean. The first group of fields is surfaced to customers on the Link Agent details view; the rest is support-oriented detail.
      - `connection_filter` LinkAgentConnectionFilter — A Link Agent's destination allow/deny connection policy.
        - `mode` 'allow' | 'deny' | 'disabled' — `allow` — only listed destinations are reachable; `deny` — listed destinations are refused and all others allowed; `disabled` — no filtering, every destination allowed.
        - `destinations` string[] — The host/port rules the mode applies to — `host`, `host:port`, `:port`, IPv4/IPv6 literals, and CIDR ranges (IPv6 bracketed as `[addr]:port`).
      - `proxy_mode` 'all' | 'mabl' | 'upstream' | 'none' — Which classes of the Agent's traffic route through the configured HTTP proxy: `all` — both the mabl control-plane connection and customer-test-target traffic; `mabl` — only the Agent's connection to mabl; `upstream` — only customer-test-target traffic; `none` — no proxy.
      - `http_proxy` string — The configured upstream HTTP CONNECT proxy as `host:port`. Credentials, if any, are omitted — see `proxy_auth_configured`. Absent when no fixed proxy is configured.
      - `proxy_pac_url` string — The proxy auto-configuration (PAC) URL the Agent evaluates to pick a proxy per destination, when PAC-based proxying is configured. Absent for fixed-proxy or no-proxy setups.
      - `proxy_exclusions` string[] — Destinations that bypass the proxy and are dialed directly — suffix, IPv4, IPv6, or CIDR expressions.
      - `proxy_auth_configured` boolean — Whether proxy credentials (fixed-proxy or PAC) are configured. The credentials themselves are never reported; this flag lets support confirm auth is present when diagnosing a 407 from the customer's proxy.
      - `upstream_proxy` LinkAgentUpstreamProxy — A fixed upstream HTTP CONNECT proxy applied to customer-test-target dials, with its bypass list. Credentials are never included.
        - `host` string — Proxy host.
        - `port` integer — Proxy port.
        - `exclusions` string[] — Destinations that bypass this proxy and are dialed directly.
      - `mablnet_update_channel` string — The release channel this Agent pulls its mablnet worker binary from — e.g. `stable`, `latest`, or a `branch:<name>` channel.
      - `auto_updates_enabled` boolean — Whether the Agent applies mablnet worker auto-updates.
      - `tunnels` string[] — The unique set of tunnel names served by this same Agent process. A single Agent instance can serve several tunnels; every one of that instance's LinkAgent records reports the same set, so any one of them shows the full picture.
      - `log_level` string — The Agent's configured logging level. Support diagnostics.
      - `branch` string — The build branch the Agent binary was produced from. Support diagnostics.
      - `max_concurrent_connections` integer — Configured cap on concurrent tunnel connections. Support diagnostics.
      - `max_connection_attempts` integer — Configured cap on connection retry attempts. Support diagnostics.
      - `shutdown_drain_timeout_seconds` integer — How long the Agent drains in-flight work on shutdown before exiting. Support diagnostics.
      - `mablnet_router` string — The configured mablnet Router endpoint override as `host:port`, if any. Support diagnostics.
      - `mablnet_peer_ca_path` string — Filesystem path to the mablnet peer CA override, if configured. Support diagnostics.
      - `mablnet_update_pull_interval_seconds` integer — How often the Agent polls for a new mablnet worker build, in seconds. Support diagnostics.
    - `operator_debug_queue` LinkAgentOperatorDebugRequest[] — Debug requests queued by mabl ops for this Agent to execute on its next poll. Bounded — the api rejects enqueues that would exceed `maxItems`. The Agent drains entries on its next tick, executes idempotently (and short-circuits any `request_id` it's seen recently), and acks the outcome via `operator_debug_results` on the next heartbeat. The api drops the matching queue entry on ack so the queue stays self-draining.
      - `request_id` string, required — Opaque caller-supplied identifier (recommended: ULID). Agent dedupes by this value and surfaces it back on the ack.
      - `action` 'dump-state' | 'redial-carriers' | 'redial-carrier' | 'force-peer-ca-refetch' | 'synthetic-dial', required — Discriminator. Each value maps to a typed sub-schema with that action's argument shape.
      - `requested_at` string, date-time, required — When the api enqueued this request.
      - `requested_by` string — Operator identifier (e.g. mabl user id). Audit-only — not used for authorization.
    - `operator_debug_results` LinkAgentOperatorDebugResult[] — Acks pushed by the Agent on heartbeat for previously-queued debug requests. The api matches by `request_id`, drops the matching queue entry, and surfaces the outcome to whichever caller originally enqueued the request.
      - `request_id` string, required — Matches the `request_id` from the corresponding `LinkAgentOperatorDebugRequest`.
      - `outcome` 'success' | 'failure' | 'skipped-duplicate' | 'unknown-action', required — Agent-side outcome. `skipped-duplicate` = Agent saw this id in its recent-requests LRU and short-circuited; `unknown-action` = Agent's version of the schema doesn't know the requested action (older Agent + newer api).
      - `detail` string — Free-form additional context. PII-sanitised on the Agent side — categories and identifiers only, no payload contents.
      - `completed_at` string, date-time, required — When the Agent finished executing this request.
    - `workspace_id` string — The workspace that owns this Link Agent, for `workspace`-owned tunnels. Unset for `company`-owned agents (mablnet-only) — read `owner_type`/`owner_id` for the canonical scope.
    - `owner_type` 'workspace' | 'company' | 'user' — The kind of entity a mablnet tunnel is scoped to — its "owner". `workspace` is the classic scope: one tunnel per workspace. `company` scopes a single tunnel to an entire company, so every workspace under that company shares one mablnet tenant (and one Link Agent fleet) instead of needing a tunnel each. `user` is a personal tunnel scoped to a single user, private to that user — its egress is the user's own machine, used to route a cloud test run through it. All three are mablnet-only: legacy Link Tunnel is always workspace-scoped.
    - `owner_id` string — The id of the owning entity — equals `workspace_id` for `workspace`-owned agents, the company id for `company`-owned agents.
  - `cursor` string — Cursor used for pagination

## Other responses

- `400` — Invalid or missing parameter
- `401` — User not authenticated
- `403` — User not authorized
- `default` — Unknown error

---

[API](https://skmtc.net/mabl/apis/mabl-api.md) · [All operations](https://skmtc.net/mabl/apis/mabl-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/mabl/mabl-api/revisions/d0fc0114da66/schema)
