> ## Documentation Index
> Fetch the complete documentation index at: https://docs.moda.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Analytics

> GET /overview for headline KPIs, /clusters for the use-case hierarchy, and /task-clusters/search for finding cluster nodes.

These endpoints read tenant-level aggregates: the headline KPIs shown on the dashboard home page and the hierarchical use-case clusters. New conversations are assigned to existing clusters continuously; the hierarchy itself is rebuilt automatically as traffic evolves, and new cluster nodes appear once a rebuild has run.

## GET /overview

Headline KPIs for a window, plus top clusters and recent activity.

### Query parameters

<ParamField query="days_back" type="integer" default="7">
  1–90. Applies to the conversation, frustration, and tool-failure KPIs.
</ParamField>

### Response fields

<ResponseField name="period" type="object">
  `{days}` — the effective window.
</ResponseField>

<ResponseField name="conversations" type="object">
  `total` in the window and `trend_pct` — percentage change versus the previous window of equal length (one decimal; `0` when the previous window was empty).
</ResponseField>

<ResponseField name="frustrations" type="object">
  `total_analyzed`, `frustrated`, `at_risk` (risk score at least 0.5 but not frustrated), `rate_pct`.
</ResponseField>

<ResponseField name="tool_failures" type="object">
  `total` (failed calls), `conversations`, `tools`.
</ResponseField>

<ResponseField name="top_clusters" type="array">
  Up to 10 clusters from the latest clustering run, largest first: `node_id`, `label`, `summary`, `keywords[]`, `segment_count`.
</ResponseField>

<ResponseField name="recent_activity" type="array">
  The 10 most recent conversation summaries: `conversation_id`, `summary`, `timestamp`. Always covers the last 7 days, regardless of `days_back`.
</ResponseField>

### Example

```bash theme={"dark"}
curl "https://moda.dev/api/v1/data/overview?days_back=30" \
  -H "x-api-key: YOUR_MODA_API_KEY"
```

```json theme={"dark"}
{
  "period": { "days": 30 },
  "conversations": { "total": 5417, "trend_pct": 8.2 },
  "frustrations": { "total_analyzed": 4903, "frustrated": 171, "at_risk": 64, "rate_pct": 3.5 },
  "tool_failures": { "total": 240, "conversations": 188, "tools": 9 },
  "top_clusters": [
    { "node_id": "node_3", "label": "Billing and refunds", "summary": "Users ask about invoices, plan changes, and refunds.", "keywords": ["refund", "invoice", "billing"], "segment_count": 812 },
    { "node_id": "node_17", "label": "Integrations setup", "summary": "Users connect calendars, CRMs, and ticketing tools.", "keywords": ["oauth", "connect", "integration"], "segment_count": 655 }
  ],
  "recent_activity": [
    { "conversation_id": "conv_9f2c41d08ab37e51", "summary": "User asks how to request a refund for an annual plan; agent explains the policy and opens a ticket.", "timestamp": "2026-08-14T09:42:11" }
  ]
}
```

## GET /clusters

Browses the hierarchical use-case clusters from the latest completed clustering run. Call it without parameters for the root level; pass a node's `node_id` as `parent_id` to descend.

### Query parameters

<ParamField query="parent_id" type="string">
  Return the children of this node. Omit for root-level nodes. The response's `breadcrumb` traces the path from the root to this node.
</ParamField>

<ParamField query="time_range" type="string" default="all">
  Accepted and validated, but the hierarchy currently reflects the latest clustering run regardless of this value.
</ParamField>

### Response fields

<ResponseField name="cluster_run" type="object | null">
  The latest completed run: `id`, `num_categories`, `num_clusters`, `num_segments`, `completed_at`. `null` when no taxonomy exists yet — the first one is built once at least 50 analyzed segments exist.
</ResponseField>

<ResponseField name="breadcrumb" type="array">
  `{node_id, label}` path from the root to `parent_id`. Empty when `parent_id` is omitted.
</ResponseField>

<ResponseField name="clusters" type="array">
  Child nodes ordered by size: `node_id`, `label`, `summary`, `keywords[]`, `node_type`, `segment_count`, `has_children`, `depth`.
</ResponseField>

<ResponseField name="meta" type="object">
  `total_clusters` and `total_segments` for the whole run.
</ResponseField>

### Example

```bash theme={"dark"}
curl "https://moda.dev/api/v1/data/clusters?parent_id=node_3" \
  -H "x-api-key: YOUR_MODA_API_KEY"
```

```json theme={"dark"}
{
  "cluster_run": {
    "id": "v2_7f3d92e4-51b8-4c26-9a07-d1e5f2b8c634_20260809_020001_4e9a1c7b",
    "num_categories": 12,
    "num_clusters": 84,
    "num_segments": 10231,
    "completed_at": "2026-08-09T02:41:05"
  },
  "breadcrumb": [
    { "node_id": "node_3", "label": "Billing and refunds" }
  ],
  "clusters": [
    { "node_id": "node_47", "label": "Refund requests", "summary": "Users ask for full or partial refunds.", "keywords": ["refund", "money back"], "node_type": "cluster", "segment_count": 388, "has_children": false, "depth": 1 },
    { "node_id": "node_58", "label": "Invoice questions", "summary": "Users ask for invoices or billing history.", "keywords": ["invoice", "receipt"], "node_type": "cluster", "segment_count": 262, "has_children": false, "depth": 1 }
  ],
  "meta": { "total_clusters": 84, "total_segments": 10231 }
}
```

## GET /clusters/:nodeId/conversations

Lists conversations whose segments belong to one cluster node.

<Note>
  An unknown `nodeId` returns `200` with `cluster: null` and an empty `conversations` array — not `404`. Check `cluster` before reading the list.
</Note>

### Query parameters

<ParamField query="limit" type="integer" default="10">
  1–100.
</ParamField>

<ParamField query="offset" type="integer" default="0">
  0–10,000.
</ParamField>

### Response fields

<ResponseField name="cluster" type="object | null">
  `node_id`, `label`, `summary`, `segment_count` — or `null` when the node does not exist in the latest run.
</ResponseField>

<ResponseField name="conversations" type="array">
  `conversation_id`, `summary`, `message_count`.
</ResponseField>

<ResponseField name="pagination" type="object">
  `limit`, `offset`, `total`, `has_more`.
</ResponseField>

### Example

```bash theme={"dark"}
curl "https://moda.dev/api/v1/data/clusters/node_47/conversations?limit=2" \
  -H "x-api-key: YOUR_MODA_API_KEY"
```

```json theme={"dark"}
{
  "cluster": {
    "node_id": "node_47",
    "label": "Refund requests",
    "summary": "Users ask for full or partial refunds.",
    "segment_count": 388
  },
  "conversations": [
    { "conversation_id": "conv_9f2c41d08ab37e51", "summary": "User asks how to request a refund for an annual plan; agent explains the policy and opens a ticket.", "message_count": 18 },
    { "conversation_id": "conv_77e0b3a94c12f8d6", "summary": "User requests a partial refund after downgrading mid-cycle.", "message_count": 9 }
  ],
  "pagination": { "limit": 2, "offset": 0, "total": 341, "has_more": true }
}
```

## GET /task-clusters/search

Searches cluster nodes in the latest hierarchy by name or meaning. Use it to resolve a phrase like "refund requests" to a `node_id` you can pass to `/clusters/:nodeId/conversations` or the `cluster_id` filter on [`/conversations`](/data-api/conversations).

### Query parameters

<ParamField query="q" type="string">
  Search text, 2–200 characters. Required unless `node_id` is given.
</ParamField>

<ParamField query="node_id" type="string">
  Deterministic lookup of one node (returns that node with its ancestor path). Makes `q` optional.
</ParamField>

<ParamField query="mode" type="string" default="hybrid">
  `fuzzy`, `semantic`, or `hybrid`.
</ParamField>

<ParamField query="limit" type="integer" default="30">
  1–50.
</ParamField>

### Response fields

<ResponseField name="clusterRunId" type="string | null">
  The run the matches came from. `null` (with empty `matches`) when no clustering run exists yet.
</ResponseField>

<ResponseField name="search_mode" type="string">
  The mode that actually served the request. Falls back to `fuzzy` when semantic search is unavailable.
</ResponseField>

<ResponseField name="matches" type="array">
  Each match has `node_id`, `parent_id`, `depth`, `node_type`, `label`, `description`, `keywords[]`, `segment_count`, `ancestor_path[]` (`{nodeId, label, depth}` from the root), `path_truncated`, and `similarity` (semantic/hybrid matches only).
</ResponseField>

### Example

```bash theme={"dark"}
curl "https://moda.dev/api/v1/data/task-clusters/search?q=refunds&limit=1" \
  -H "x-api-key: YOUR_MODA_API_KEY"
```

```json theme={"dark"}
{
  "clusterRunId": "v2_7f3d92e4-51b8-4c26-9a07-d1e5f2b8c634_20260809_020001_4e9a1c7b",
  "query": "refunds",
  "search_mode": "hybrid",
  "matches": [
    {
      "node_id": "node_47",
      "parent_id": "node_3",
      "depth": 1,
      "node_type": "cluster",
      "label": "Refund requests",
      "description": "Users ask for full or partial refunds.",
      "keywords": ["refund", "money back"],
      "segment_count": 388,
      "ancestor_path": [
        { "nodeId": "node_3", "label": "Billing and refunds", "depth": 0 }
      ],
      "path_truncated": false,
      "similarity": 0.83
    }
  ]
}
```

## Next steps

* [Conversations](/data-api/conversations) — filter the conversation list by `cluster_id`.
* [Dashboard: Use Cases](/dashboard/use-cases) — the same hierarchy in the dashboard, including how the taxonomy is built.
* [Problems](/data-api/problems) — ranked root-cause problems across signals.
