> ## 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.

# Conversations

> GET /conversations for filtered lists, /context for windowed messages, /step-scores for outcome scores, and /world-state for slots, threads, and state events.

These endpoints read individual conversations: list and filter them, pull a window of messages around a specific turn, read outcome step scores, and inspect a conversation's [world state](/concepts/data-model).

## GET /conversations

Lists conversations, newest first, with optional text search and structured filters.

### Query parameters

<ParamField query="search" type="string">
  Free-text search over conversation summaries and message bodies. When `search` is the only filter, results may be relevance-ranked (see `search_mode` below).
</ParamField>

<ParamField query="cluster_id" type="string">
  Restrict to conversations assigned to one use-case cluster node (see [Analytics](/data-api/analytics)).
</ParamField>

<ParamField query="user_id" type="string">
  Restrict to conversations from one end user.
</ParamField>

<ParamField query="time_range" type="string" default="all">
  One of `all`, `1h`, `24h`, `3d`, `7d`, `30d`, `90d`. Filters on the conversation's last activity.
</ParamField>

<ParamField query="environment" type="string" default="all">
  One of `all`, `development`, `staging`, `production`.
</ParamField>

<ParamField query="world_state" type="string">
  Keyword(s) matched case-insensitively against the conversation's world-state content (segment slots and carried-in durable profile slots). Comma-separated terms are ANDed. Free text, not `key=value`. Maximum 200 characters.
</ParamField>

<ParamField query="outcome" type="string" default="any">
  `any`, `positive`, or `negative`. `positive` means the conversation's blended segment outcome score is high and no frustration was detected; `negative` means a low blended score or a frustration detection.
</ParamField>

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

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

### Response fields

<ResponseField name="conversations" type="array">
  Each row has `conversation_id`, `summary`, `message_count`, `first_timestamp`, `last_timestamp`, `cluster_id` and `cluster_name` (or `null`), and `environment`.
</ResponseField>

<ResponseField name="search_mode" type="string">
  `keyword` or `hybrid`. `hybrid` runs only when `search` is set with no structured filters and semantic search is available; otherwise the deterministic keyword path serves the request.
</ResponseField>

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

<Note>
  When `search_mode` is `hybrid`, `pagination.total` is the size of the relevance-ranked candidate pool, not an exact corpus count. Branch on `search_mode` before treating `total` as an exact figure — in `keyword` mode it is exact.
</Note>

### Example

```bash theme={"dark"}
curl "https://moda.dev/api/v1/data/conversations?time_range=7d&environment=production&outcome=negative&limit=2" \
  -H "x-api-key: YOUR_MODA_API_KEY"
```

```json theme={"dark"}
{
  "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,
      "first_timestamp": "2026-08-14T09:12:03",
      "last_timestamp": "2026-08-14T09:41:47",
      "cluster_id": "node_3",
      "cluster_name": "Billing and refunds",
      "environment": "production"
    },
    {
      "conversation_id": "conv_5d1a7be2c90f4438",
      "summary": "User cannot connect a calendar integration; agent retries the OAuth flow twice without success.",
      "message_count": 31,
      "first_timestamp": "2026-08-13T17:55:20",
      "last_timestamp": "2026-08-13T18:20:44",
      "cluster_id": "node_17",
      "cluster_name": "Integrations setup",
      "environment": "production"
    }
  ],
  "search_mode": "keyword",
  "pagination": { "limit": 2, "offset": 0, "total": 57, "has_more": true }
}
```

## GET /conversations/:id/context

Returns a window of parsed messages centered on one message index. Use it to read the turns around a [search](/data-api/search) hit or a signal's key turn.

### Query parameters

<ParamField query="msg_index" type="integer">
  Zero-based index of the center message. Defaults to the middle of the conversation. Out-of-range values are clamped to the last message.
</ParamField>

<ParamField query="window" type="integer" default="2">
  Messages to include on each side of the center, 1–5.
</ParamField>

### Response fields

<ResponseField name="conversation_id" type="string" />

<ResponseField name="total_messages" type="integer">
  Total messages stored for the conversation.
</ResponseField>

<ResponseField name="summary" type="string">
  One-line conversation summary; empty string if none has been generated yet.
</ResponseField>

<ResponseField name="context" type="object">
  `center_index`, `from_index`, `to_index`, and `messages[]`. Each message has `index`, `role`, `content`, `tool_calls[]` (`id`, `name`, `input` — input is a truncated string), `tool_results[]` (`tool_use_id`, `content` — truncated, `is_error`), and `timestamp`.
</ResponseField>

### Example

```bash theme={"dark"}
curl "https://moda.dev/api/v1/data/conversations/conv_9f2c41d08ab37e51/context?msg_index=6&window=1" \
  -H "x-api-key: YOUR_MODA_API_KEY"
```

```json theme={"dark"}
{
  "conversation_id": "conv_9f2c41d08ab37e51",
  "total_messages": 18,
  "summary": "User asks how to request a refund for an annual plan; agent explains the policy and opens a ticket.",
  "context": {
    "center_index": 6,
    "from_index": 5,
    "to_index": 7,
    "messages": [
      {
        "index": 5,
        "role": "assistant",
        "content": "You can request a refund from the billing page. Would you like me to open a ticket?",
        "tool_calls": [],
        "tool_results": [],
        "timestamp": "2026-08-14T09:28:10"
      },
      {
        "index": 6,
        "role": "user",
        "content": "I already asked about the refund policy twice and the bot keeps linking the same page.",
        "tool_calls": [],
        "tool_results": [],
        "timestamp": "2026-08-14T09:31:22"
      },
      {
        "index": 7,
        "role": "assistant",
        "content": "",
        "tool_calls": [
          { "id": "toolu_01Xk2m", "name": "create_ticket", "input": "{\"topic\":\"refund\",\"plan\":\"annual\"}" }
        ],
        "tool_results": [],
        "timestamp": "2026-08-14T09:31:40"
      }
    ]
  }
}
```

## GET /conversations/:id/step-scores

Returns outcome step scores for one conversation: per-segment score curves and a weighted rollup of the segments' closing scores. These are the scores behind the `outcome` filter on `GET /conversations`.

### Response fields

<ResponseField name="conversation_id" type="string" />

<ResponseField name="segments" type="array">
  One entry per scored thread segment, scoped to that segment's newest scoring pass. Each has `segment_id`, `thread_id`, `segment_status_at_scoring`, `segment_unit_count`, scoring metadata (`member_hash`, `model_id`, `feature_mode`, `scored_at`), `failure_type` (`{failure_type, score}` or `null`), and `steps[]` — each step with `unit_id`, `message_index`, `unit_index`, `node_type`, `progress_score`, `first_bad_step_probability`, `first_bad_step_reason`, `graph_id`, `node_index`.
</ResponseField>

<ResponseField name="rollup" type="object">
  Weighted blend of the segments' closing scores: `blended_score`, `segment_count`, `weighted_segment_count`, `total_weight`. `null` until the conversation has segment scores that carry weight.
</ResponseField>

<ResponseField name="model / failure_type / first_bad_step / steps" type="legacy fields">
  Whole-conversation fields populated only for conversations scored before per-segment scoring existed; for new conversations they are `null` and `steps` is empty. Read `segments` and `rollup` instead.
</ResponseField>

### Example

```bash theme={"dark"}
curl "https://moda.dev/api/v1/data/conversations/conv_9f2c41d08ab37e51/step-scores" \
  -H "x-api-key: YOUR_MODA_API_KEY"
```

```json theme={"dark"}
{
  "conversation_id": "conv_9f2c41d08ab37e51",
  "model": null,
  "failure_type": null,
  "first_bad_step": null,
  "steps": [],
  "segments": [
    {
      "segment_id": "c7e5a1d2-4f6b-5a38-9c21-8b0e3d47f6a9",
      "thread_id": "refund_ticket",
      "segment_status_at_scoring": "resolved",
      "segment_unit_count": 9,
      "member_hash": "5f2d90c1a4b7e832d94a0c6e1b7f3852e0d9c4a71b6f2e83d5a90c1b4e7f2680",
      "model_id": "b1c9e2d4-77aa-4f30-9d2f-4a1e8c6b5d90",
      "feature_mode": "graph_segment",
      "scored_at": "2026-08-14T09:46:02Z",
      "failure_type": null,
      "steps": [
        {
          "unit_id": "3e8b1c5d9f2a70648e0c3b7a1d5f2946c8a0e3d71f5b2c69a4e8d0b3f7c1a25e",
          "message_index": 6,
          "unit_index": 4,
          "node_type": "agent_message",
          "progress_score": 0.41,
          "first_bad_step_probability": 0.12,
          "first_bad_step_reason": "",
          "graph_id": "7d0c2b6e-14f3-49a1-8e57-cc90ab21d743",
          "node_index": 4
        }
      ]
    }
  ],
  "rollup": {
    "blended_score": 0.63,
    "segment_count": 2,
    "weighted_segment_count": 2,
    "total_weight": 1.8
  }
}
```

## GET /conversations/:id/world-state

Returns the conversation's world state: tracked facts (slots), open and resolved threads, the per-message state event log, and the end user's durable cross-conversation profile. This is the same data as the World State view on the conversation detail page.

<Note>
  The world-state endpoints return camelCase field names (`hasData`, `segmentIndex`, ...), unlike the snake\_case used by the rest of the Data API.
</Note>

### Query parameters

<ParamField query="summary_only" type="string">
  Set to `1` to return `segments` and `events` as empty arrays and add `segmentSummaries` — per-segment slot summaries (`segmentIndex`, `durableCount`, `topSlots`). Cheaper for a quick existence check.
</ParamField>

<ParamField query="event_limit" type="integer" default="2000">
  Maximum state events to return, 1–5,000.
</ParamField>

### Response fields

<ResponseField name="hasData" type="boolean">
  `false` when no world state has been built for the conversation yet (world state is built within minutes of ingest).
</ResponseField>

<ResponseField name="userId" type="string | null">
  The end user the conversation belongs to, when known.
</ResponseField>

<ResponseField name="userDurableProfile" type="object | null">
  The user's current cross-conversation profile: `slots` (map of slot key to `{value, source, ...}`), `updatedAt`, `slotCount`.
</ResponseField>

<ResponseField name="conversationWarmstart" type="object | null">
  Durable slots that were loaded at the start of this conversation.
</ResponseField>

<ResponseField name="segments" type="array">
  One entry per thread segment: `segmentIndex`, `segmentId`, `startMsgIdx`, `endMsgIdx`, `slots`, `carryIn`, `openThreads[]` (`id`, `status` — `open` / `resolved` / `cancelled`, `summary`, `goal`, `timeline[]`, `subgoals[]`), and `slotCounts` (`durable`, `segmentLocal`, `total`).
</ResponseField>

<ResponseField name="events" type="array">
  Per-message state changes: `eventId`, `segmentIndex`, `segmentId`, `msgIndex`, `blockIndex`, `eventType`, `slotKey`, `oldValue`, `newValue`, `provenance`.
</ResponseField>

<ResponseField name="summary" type="object">
  `eventCount`, `segmentCount`, `lastUpdatedAt`.
</ResponseField>

### Example

```bash theme={"dark"}
curl "https://moda.dev/api/v1/data/conversations/conv_9f2c41d08ab37e51/world-state?event_limit=100" \
  -H "x-api-key: YOUR_MODA_API_KEY"
```

```json theme={"dark"}
{
  "hasData": true,
  "userId": "user_4821",
  "userDurableProfile": {
    "slots": {
      "user.plan": { "value": "annual", "source": "user", "msg_index": 2 }
    },
    "updatedAt": "2026-08-14T09:41:50",
    "slotCount": 1
  },
  "conversationWarmstart": {
    "user.plan": { "value": "annual", "source": "user" }
  },
  "segments": [
    {
      "segmentIndex": 0,
      "segmentId": "c7e5a1d2-4f6b-5a38-9c21-8b0e3d47f6a9",
      "startMsgIdx": 0,
      "endMsgIdx": 11,
      "slots": {
        "refund.reason": { "value": "unused seats", "msg_index": 4, "segment_local": true }
      },
      "carryIn": {
        "user.plan": { "value": "annual", "source": "user" }
      },
      "openThreads": [
        {
          "id": "refund_ticket",
          "status": "resolved",
          "summary": "Open a refund ticket for the annual plan",
          "timeline": [
            { "text": "Ticket created", "msg_index": 7, "tool_name": "create_ticket" }
          ],
          "subgoals": []
        }
      ],
      "slotCounts": { "durable": 1, "segmentLocal": 1, "total": 2 }
    }
  ],
  "events": [
    {
      "eventId": "b3a1f0d2-6c4e-4b8a-9f21-7e5d0c9a8b34",
      "segmentIndex": 0,
      "segmentId": "c7e5a1d2-4f6b-5a38-9c21-8b0e3d47f6a9",
      "msgIndex": 4,
      "blockIndex": 0,
      "eventType": "slot_set",
      "slotKey": "refund.reason",
      "oldValue": "",
      "newValue": "unused seats",
      "provenance": "user"
    }
  ],
  "summary": { "eventCount": 1, "segmentCount": 1, "lastUpdatedAt": "2026-08-14T09:41:50" }
}
```

### GET /conversations/:id/world-state/replay

Returns the folded state frame by frame, one frame per message — the data behind the state replay view.

<ParamField query="message_count" type="integer" default="0">
  Number of messages to replay through; `0` replays the full history.
</ParamField>

<ParamField query="format" type="string" default="frames">
  `frames` returns one folded frame per message, capped at 5,000 frames. `fold_inputs` returns raw keyframes plus event deltas for client-side folding, with no frame cap.
</ParamField>

With `format=frames`, the response is `{hasData, messageCount, frames[]}` where each frame has `msgIndex`, `segmentIndex`, `blockIndex`, `slots`, `slotsJson` (the slot map serialized as a string), `openThreads`, and `hasSnapshot` (`true` when state changed at that message).

### GET /conversations/:id/world-state/snapshot

Returns the folded slot state at one position.

<ParamField query="msg_index" type="integer">
  Message index to snapshot at.
</ParamField>

<ParamField query="block_index" type="integer">
  Content-block index within the message.
</ParamField>

<ParamField query="segment_index" type="integer">
  Optional segment scope.
</ParamField>

```bash theme={"dark"}
curl "https://moda.dev/api/v1/data/conversations/conv_9f2c41d08ab37e51/world-state/snapshot?msg_index=7" \
  -H "x-api-key: YOUR_MODA_API_KEY"
```

```json theme={"dark"}
{
  "found": true,
  "segmentIndex": 0,
  "msgIndex": 7,
  "blockIndex": 0,
  "slots": {
    "user.plan": { "value": "annual", "source": "user" },
    "refund.reason": { "value": "unused seats", "msg_index": 4, "segment_local": true }
  }
}
```

## Next steps

* [Search](/data-api/search) — find the message index to center a context window on.
* [Data model](/concepts/data-model) — what conversations, segments, slots, and threads mean.
* [Dashboard: Conversations](/dashboard/conversations) — the same data in the Full Trace and World State views.
