# 3pm-backend route `nodeAppMetrics`

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: 1175
  o200k: 735
# GET /api/buckets/{clusterId}/nodes/{nodeId}/app-metrics (nodeAppMetrics)

The application metrics of every instance of the node, as last received: sources, series, values and histogram buckets.

- 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:read; that node's bucket only, and for the calls it placed itself
- answer: AppMetricsView (JSON)

## Types

```ts
/** `GET /api/buckets/{id}/nodes/{node}/app-metrics`: what the hub last applied for every instance of the node. */
interface AppMetricsView {
  node_id: NodeId;
  instances: AppInstanceView[];
  /** States refused since the backend first held the node: over the caps (1 000 series, 128 buckets, 100 instances) or an identity off the grammar. */
  refused: number;
}

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

interface AppInstanceView {
  /** The daemon's process id. */
  id: number;
  /** Incarnation: a restart starts a new one, with new series ids. */
  gen: number;
  name: string;
  instance: number;
  ns?: string;
  /** The state's revision, as the daemon counts it. */
  rev: number;
  /** The agent's clock on the frame that last moved the instance's metrics, epoch ms. */
  ts: number;
  sources: AppSourceView[];
  series: AppSeriesView[];
}

interface AppSourceView {
  source: string;
  pid?: number;
  /** The actions this source exposes to the backend (`nodeAction`); absent when none. */
  actions?: AppActionView[];
}

interface AppSeriesView {
  /** The daemon's series id, unique per incarnation. */
  sid: number;
  /** The app's name for it (`http.latency`). */
  name: string;
  kind: AppMetricKind;
  /** UCUM (`ms`, `By`, `1`). */
  unit?: string;
  description?: string;
  /** The `source` that owns it. */
  source?: string;
  labels?: Record<string, string>;
  /** A gauge's or counter's value. */
  value?: number;
  /** A histogram's. */
  hist?: AppHistView;
}

interface AppActionView {
  name: string;
  description?: string;
  /** The declared timeout, ms (30 s when absent). */
  timeout_ms?: number;
  /** The declared `output.max_bytes`: what a call asking for the file reserves of its caller's blob quota (256 MiB when absent). */
  output_max_bytes?: number;
}

type AppMetricKind = "gauge" | "counter" | "histogram" | "unknown";

/** An exponential histogram: bucket `i` covers `(base^i, base^(i+1)]` with `base = 2^(2^-scale)`. */
interface AppHistView {
  scale: number;
  count: number;
  sum: number;
  /** Observations equal to zero. */
  zero: number;
  /** `[index, count]`, index ascending, non-empty buckets only. */
  buckets: [number, number][];
}
```
