# 3pm-backend route `getBucket`

Paths are relative to the backend's origin (https://fleet.edgecr4ft.net, or your own 3pm-backend), not to this site. Index: https://3pm.edgecr4ft.net/docs/backend/api.md

---

tokens:
  claude: 5397
  o200k: 3256
# GET /api/buckets/{clusterId} (getBucket)

One bucket with its nodes, last telemetry and history — the shape the feed opens with; `history`, `match` and `view` cut it as they cut the feed's `init`.

- auth: an account — Authorization: Bearer 3pmu_<token>, or the dashboard's session cookie
- node: also open to a node's own bearer (3pma_…) when its row is granted fleet:list; that node's bucket only, and for the calls it placed itself
- answer: BucketDetail (JSON)

## Details

The optional URL parameters of `events`, applied to this answer as to the feed's `init`: `history=0` sends `history` empty, `match=<text>` keeps the nodes whose hostname contains `<text>` (and their snapshots and history), `view=live` cuts each snapshot to the host's gauges and a short row per process. Without them the answer is whole, as it always was. A value it does not know (`history=2`, `view=lite`) is 400 `ERR_BAD_REQUEST`. The generated method takes them in `options.query`: `api.getBucket(clusterId, { query: { history: 0, view: "live" } })`. Example: `GET /api/buckets/3pmc_…?history=0&view=live`.

## Types

```ts
/** `GET /api/buckets/{id}`, and the feed's `init`. */
interface BucketDetail {
  bucket: BucketView;
  /** `host:port` the pasted lines point at. */
  advertise: string;
  nodes: NodeView[];
  /** The last telemetry frame of each node the hub has heard from. */
  snapshots: Telemetry[];
  /** Per node, per process: the recent samples. */
  history: NodeHistory[];
}

interface BucketView {
  cluster_id: ClusterId;
  name: string;
  created_at: EpochMs;
  /** Does a host that links with a reusable token wait for `approveNode`? `false`: a valid token of the bucket is enough (`setBucketSettings`). For the owner only — absent from what a node's bearer reads. */
  approval?: boolean;
  /** Does a node ask of the bucket only what it was granted (`setNodeAccess`)? `false`: every active node may place a call on every other, with no scope granted node by node (`setBucketSettings`). For the owner only, like `approval`. */
  require_access_rights?: boolean;
  /** Is the screen of each `3pm fleet ssh` / `fleet attach` session kept as an asciicast artifact of its call (`setBucketSettings`)? On by default. For the owner only, like `approval`. */
  record_shells?: boolean;
}

interface NodeView {
  node_id: NodeId;
  /** What to display: enrollment first, presence for an active node. */
  state: DisplayState;
  /** The database state alone. */
  enrollment: NodeState;
  hostname: string;
  os: string;
  arch: string;
  agent_version: string;
  /** Which token admitted the host. */
  token_id?: string;
  /**
   * What the host owner lets be asked of this node, exactly as its last
   * `hello` declared it (`link.toml` `caps`). Empty means the node
   * accepts nothing — or, for a node that has not linked since it
   * enrolled, that it has not said yet.
   */
  caps: Capability[];
  /** What this node may ask of the bucket with its own bearer; empty by default. */
  scopes: NodeScope[];
  /** Who may place a call on this node: empty, every node holding `fleet:invoke`; with rules, what a rule admits. */
  callers: CallerRule[];
  /** What the node lets go agent to agent with a LAN neighbour: nothing, a call's files (the default), or its live legs too. */
  direct: DirectMode;
  enrolled_at: EpochMs;
  approved_at?: EpochMs;
  revoked_at?: EpochMs;
  last_seen_at?: EpochMs;
  last_auth_error?: AuthError;
  /** Bytes received over the node's link per second, averaged over the last 5s. */
  rx_bps: number;
  /** Bytes received since the backend started. */
  rx_bytes: number;
  /** Where the node's last link came from, as this backend saw it (behind `--trust-proxy`, the forwarded client); absent until the node linked since the backend started. */
  peer_ip?: string;
  /** The private addresses of the node's interfaces (RFC 1918, CGNAT, IPv6 ULA; container bridges left out), from its last complete host snapshot. */
  private_ips?: string[];
  /** The account the node's daemon runs as, from its last complete host snapshot; absent until it sent one. */
  user?: string;
  /** The accounts with an open login session on the node, from its last complete host snapshot. Sent to the bucket's owner and to a node holding `fleet:read`; absent otherwise, which is not the same as nobody being logged in. */
  sessions?: string[];
}

/** One `telemetry` event. */
interface Telemetry {
  node_id: NodeId;
  seq: number;
  ts: EpochMs;
  /** State patches of the node the backend refused so far: a gap (answered by a resync) or a section over a bound. */
  dropped: number;
  snap: TelemetrySnapshot;
}

interface NodeHistory {
  node_id: NodeId;
  procs: ProcHistory[];
}

/** `3pmc_…` — a bucket. */
type ClusterId = string;

/** Milliseconds since the Unix epoch. */
type EpochMs = number;

/** `3pmn_…` — a node. */
type NodeId = string;

/** `NodeView.state`: the enrollment state, or the presence of an active node. */
type DisplayState = NodeState | "online" | "stale" | "offline";

/** Enrollment state in the database, presence aside. */
type NodeState = "active" | "pending" | "revoked" | "unknown";

/** What the host owner lets the backend ask of a node, as its `hello` declared it. */
type Capability = "metrics:read" | "proc:restart" | "proc:stop" | "unknown";

/**
 * What a node may ask of its bucket with its own bearer, granted by the
 * owner (`setNodeAccess`): `fleet:list` the fleet as a list, `fleet:read`
 * its metrics, hosts, trees and action catalogue, `fleet:invoke` placing
 * calls. Nothing by default.
 */
type NodeScope = "fleet:list" | "fleet:read" | "fleet:invoke";

/**
 * One line of a node's `callers`: who may place a call on it once the
 * list holds a rule (an empty list accepts every node holding
 * `fleet:invoke`). `"*"` is the wildcard, and a missing `from_app` /
 * `app` / `action` means `"*"` — except an action of the `3pm:` prefix,
 * which a wildcard never reaches.
 *
 * `from_app` is an operational guard rail, not a boundary: the calling
 * application's name is declared by the calling daemon and the backend
 * cannot check it. The trust boundary is the node.
 */
interface CallerRule {
  /** The calling node: a `3pmn_…` id, or `"*"`. */
  from: string;
  from_app?: string;
  app?: string;
  action?: string;
}

/** `off`, `files` (a call's files go direct between LAN neighbours) or `all` (the default: its live legs too). */
type DirectMode = "off" | "files" | "all" | (string & {});

/** The last time a node was turned away at `/api/v1/link`. */
interface AuthError {
  /** `ERR_NODE_PENDING`, `ERR_NODE_REVOKED`, … */
  code: string;
  at: EpochMs;
}

interface TelemetrySnapshot {
  /** Volatile host gauges, identity blanked. */
  host_tick?: HostMetrics;
  /** The whole host snapshot: first frame, identity change, and periodically. */
  host_full?: HostMetrics;
  procs: RedactedRecord[];
  /** Absent while the daemon is down. */
  daemon?: DaemonInfo;
}

interface ProcHistory {
  id: number;
  name: string;
  points: Point[];
}

/**
 * Host gauges and counters. Wide and platform-dependent — see
 * `HostMetrics` in `threepm-protocol`; the fields below are the ones
 * every platform fills.
 */
interface HostMetrics {
  hostname: string;
  os_name: string;
  os_version: string;
  kernel_version: string;
  arch: string;
  uptime_s: number;
  cpu_brand: string;
  logical_cpus: number;
  global_cpu_percent: number;
  per_core_percent: number[];
  load_avg: [number, number, number];
  mem_total: number;
  mem_used: number;
  mem_available: number;
  swap_total: number;
  swap_used: number;
  processes_count: number;
  /** 3pm's own footprint, measured by the host agent; absent from an older daemon. */
  "3pmDaemon"?: ThreepmDaemon;
  /** The heaviest processes of the host, managed by 3pm or not: the ten largest by CPU, by memory and by network rate, merged and sorted by pid — sort by the column you want and take your own top. Live only (no series is kept). Absent from a daemon with the option off, or predating the table. */
  top_processes?: HostTopProcess[];
  [field: string]: unknown;
}

/** One managed process, as the agent redacts it. */
interface RedactedRecord {
  id: number;
  name: string;
  instance: number;
  namespace?: string | null;
  status: ProcStatus;
  pid?: number | null;
  /** Absent during the warm-up window after a spawn. */
  cpu_percent?: number | null;
  mem_bytes: number;
  uptime_ms?: number | null;
  restarts: number;
  unstable_restarts: number;
  listen_ports: number[];
  exit_code?: number | null;
  exit_signal?: string | null;
  created_at_epoch_ms: number;
  next_cron_epoch_ms?: number | null;
  waiting_for: string[];
  module?: string | null;
  interpreter?: string | null;
  watch: boolean;
  /** Environment variable **names** — never values. */
  env_keys: string[];
  /**
   * Live processes of the instance's tree, the spawned pid included:
   * 1 for an app running alone, absent from a daemon predating process
   * trees. The tree itself is `nodeProcessTrees`.
   */
  proc_count?: number;
  /**
   * The tree's network traffic, short keys: absent on a node with no
   * source, before the daemon's first 5 s window, and from an older
   * agent. Its history: `process.net.received` / `process.net.sent`.
   */
  net?: RedactedNet;
}

interface DaemonInfo {
  version: string;
  protocol: number;
  pid: number;
}

/** One sample of one instance: cpu and RSS of its whole process tree, as 3pm reports them. */
interface Point {
  /** Epoch seconds. */
  t_s: number;
  cpu: number;
  rss_kb: number;
}

/** What 3pm itself costs the node (`HostMetrics.3pmDaemon`). */
interface ThreepmDaemon {
  /** When the agent assembled this part, Unix ms. */
  at_epoch_ms?: number;
  /** One row per 3pm process: the daemon, then the host agent. */
  processes: ThreepmProcess[];
  /** The daemon's counters; absent for a tick the daemon did not answer. */
  runtime?: ThreepmRuntime;
}

/** One row of `HostMetrics.top_processes`. */
interface HostTopProcess {
  pid: number;
  /** The executable's name: never its path, never its arguments. */
  name: string;
  /** The account it runs as; absent when the platform does not say. */
  user?: string;
  /** Share of one core over the last window: 100 is one core busy. */
  cpu_percent: number;
  /** Resident set; shared pages are counted in every process holding them. */
  mem_bytes: number;
  /** Physical footprint (macOS): what Activity Monitor calls Memory, with the compressed pages and graphics memory the RSS misses. Never on Linux; absent on macOS for a process the source did not list. When a table has it, its memory top was ranked on it, not on `mem_bytes`. */
  footprint_bytes?: number;
  /** Bytes per second over the last window. Absent, not zero, when the node has no per-process source or the source cannot see this process. */
  net_rx_bytes_per_s?: number;
  net_tx_bytes_per_s?: number;
}

type ProcStatus =
  | "launching"
  | "online"
  | "stopping"
  | "stopped"
  | "waiting_restart"
  | "errored"
  | "unknown";

/** Network traffic as the link carries it. */
interface RedactedNet {
  /** Bytes per second received / sent over the daemon's last window, rounded to whole bytes. */
  r: number;
  t: number;
  /** Bytes received / sent since the daemon started measuring; reset by a restart or a daemon update. */
  rb: number;
  tb: number;
  /** The window lost some notifications: a lower bound. Absent when false. */
  pa?: boolean;
}

/** One 3pm process as its host agent sees it from the OS. */
interface ThreepmProcess {
  /** `daemon` or `host-agent` (the set may grow). */
  role: string;
  pid: number;
  cpu_percent: number;
  mem_bytes: number;
  uptime_ms?: number;
  /** Linux only for the daemon row on the OS side; filled from the daemon elsewhere. */
  open_fds?: number;
  /** Linux only. */
  threads?: number;
}

/** The daemon's runtime counters, the health subset of `3pm internals`. */
interface ThreepmRuntime {
  version: string;
  boot_id: number;
  uptime_ms: number;
  apps_total: number;
  online: number;
  launching: number;
  stopping: number;
  stopped: number;
  waiting_restart: number;
  errored: number;
  restarts_total: number;
  unstable_restarts_total: number;
  timers_armed: number;
  bus_subscribers: number;
  log_sinks: number;
  log_rotations_total: number;
  log_bytes_written_total: number;
  daemon_log_bytes: number;
  nofile_soft: number;
  nofile_hard: number;
  handoffs: number;
  host_agent_restarts: number;
}
```
