# List conversations that are live right now (/docs/api/voice-agents/live-conversations/listLiveConversations)

> For the complete documentation index, see [llms.txt](https://docs.tandemcx.ai/llms.txt). Any page on this site can be fetched as Markdown by appending `.md` to its URL.

`GET /v1/agent-media/live-conversations`

List conversations that are live right now

Every agent conversation the organization currently has, across both channels — voice calls and website chat. Conversations in progress come first, oldest first; idle chat conversations follow, most recently active first.

This is a point-in-time read, not a query over stored data — there is no pagination, no date range, and no way to ask for a conversation that has ended. Poll it, or hold open the streaming endpoint below to be told when it changes.

The two channels do not have the same lifetime, and `channel` is what tells you which rules a row follows. A **voice** row exists for exactly as long as its call, and disappears the moment the caller hangs up. A **chat** row persists while the conversation is still going: website chat holds no session between messages, so a conversation is listed here until it ends — the agent finished it, or it reached its message limit — or until nothing has been said for 30 minutes. Its `state` (`active` or `idle`) says whether anyone is there right now.

A chat leaving this collection does NOT mean it is unresumable. A visitor can continue a conversation for 24 hours from their last message, and one that does come back is listed again as `active`. Three windows govern a chat and they are deliberately different: 30 minutes of quiet takes it off this collection, 24 hours makes it unresumable, and 90 days is how long the visitor can still read it.

## Request

**Base URL:** `https://api.tandemcx.ai`

**Authentication:** `Authorization: Bearer <token>` — A Tandem bearer token. Requests are scoped to the organization the token belongs to; no organization header is required.

No parameters or request body.

## Responses

### 200 — The organization's live conversations.

The live conversations at the moment of the read.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `conversations` | array of object | No | — |
| `conversations[].conversationId` | string | No | For a call, the same id it is reported under once it completes, so a live conversation can be followed into its Calls record. For a chat, the conversation id the widget presents to continue the conversation. Chat conversations have no Calls record. |
| `conversations[].channel` | enum: `voice` \| `chat` | No | Which surface the conversation is on. Published rather than left inferable: a call with no caller id and a chat whose widget was deleted both carry neither `ani` nor `widgetName`. |
| `conversations[].agentId` | string | No | — |
| `conversations[].agentName` | string \| null | No | The agent's configured name, or null when it could not be resolved. Never the agent id as a stand-in. |
| `conversations[].ani` | string | No | VOICE ONLY. The caller's number, in E.164. Absent when the call carries no caller id. |
| `conversations[].widgetName` | string \| null | No | CHAT ONLY. The name of the chat widget the visitor is using — which site they are on, not who they are. A website visitor is anonymous, and no per-visitor identifier is published here. Null when the widget was deleted while a conversation it opened is still in memory. |
| `conversations[].startedAt` | string (date-time) | No | When the call was answered, or the chat conversation opened. Derive elapsed time from this rather than expecting a duration field — the value would be stale the moment it was sent. For a chat, prefer `lastActivityAt`: a conversation can sit idle for for days, so time since `startedAt` stops describing anything useful. |
| `conversations[].lastActivityAt` | string (date-time) | No | CHAT ONLY. When the conversation's last message completed. This is what `state` is derived from, and the timestamp to measure a chat conversation's age against. |
| `conversations[].state` | enum: `connecting` \| `live` \| `active` \| `idle` \| `ended` | No | Per-channel; `channel` says which pair applies, except `ended`. VOICE — `connecting` means the call is up but the agent has not started talking yet. Most calls pass through it in well under a second; one that stays there is a call the caller is hearing silence on. `live` is a call in progress. CHAT — `active` means a message landed within the last five minutes. `idle` is every other conversation still in memory, including one the visitor has abandoned. There is no `live`, because website chat holds no session between messages: the platform knows when the last turn was, not whether anyone is still reading. EITHER — `ended` is a conversation a supervisor took over whose customer left before the supervisor closed it out. It stays, with its `assignment`, until they do. |
| `conversations[].sentiment` | object | No | How the person sounds right now, read from the words they have said. Both channels carry it. This reads language, not audio — there is no tone-of-voice or emotion detection behind it, and the same scoring reads a chat's typed messages. Absent until the FIRST reading — for a call, the first caller utterance that gets scored; for a chat, its first completed turn — and on any conversation that started before this existed. After that first reading the field stays present, and changes only when the reading itself changes. A calm caller therefore carries `positive`/`none` or `neutral`/`none`, not nothing: presence is NOT a proxy for "something notable happened", so branch on the labels rather than on whether the field is there. Absence is not `neutral` either — "nothing has been heard about this one yet" and "this one has been read, and they are fine" are different facts, and a client that renders the first as the second is claiming a conversation was checked when it was not. Labels only. No transcript text, no numeric score, and no counters — this is what a board renders on a row, not an analysis of the conversation. It is a hint about where to look, not a judgement of how the conversation was handled. |
| `conversations[].sentiment.polarity` | enum: `positive` \| `neutral` \| `negative` | Yes | Which way the person's words lean. Coarse on purpose, and carried forward across turns rather than recomputed from the latest sentence alone. |
| `conversations[].sentiment.escalation` | enum: `none` \| `low` \| `high` | Yes | How hard the person is pushing to get out of the automated conversation — asking for a person, repeating themselves, and, on a call, talking over the agent. That last signal is voice-only: there is nothing to interrupt in text, so a chat's escalation is carried by the other signals alone. Deliberately independent of `polarity`, and usually the more useful of the two. A caller can be perfectly polite while asking three times to be put through to a person: that reads as `neutral` polarity and `high` escalation, and it is the case a polarity-only signal misses entirely. |
| `conversations[].alerts` | array of object | No | Reasons a supervisor might want to look at this conversation, derived from `sentiment` each time the collection is read. A suggestion, not a verdict: nothing is blocked or escalated because an alert is raised. Absent when there is nothing to raise, rather than an empty array. At most one alert of each `kind` per conversation. |
| `conversations[].alerts[].id` | string | Yes | `<conversationId>:<kind>`. The same alert keeps the same id on every read, for as long as it is raised. |
| `conversations[].alerts[].kind` | string | Yes | What raised it. Today `escalation` (the person is pushing to get out of the automated conversation) or `sentiment` (their words read negative; not raised while `escalation` is). Treat this as an open set: more kinds will be added, and a client should render one it does not recognise from `label` and `detail` rather than drop it. |
| `conversations[].alerts[].label` | string | Yes | A short name for the alert, such as `Asking for a person`. |
| `conversations[].alerts[].detail` | string | Yes | One sentence on what was observed. On a call it names what drove the alert, such as how many times the caller asked for a person. A chat carries the general wording. |
| `conversations[].dismissedAlerts` | array of object | No | Alerts a supervisor has dismissed on this conversation, with the reason, so a second supervisor can see the conversation was already looked at. Only alerts that would otherwise still be raised: one that has cleared on its own is not listed. Absent when nothing was dismissed. |
| `conversations[].dismissedAlerts[].id` | string | Yes | `<conversationId>:<kind>`. The same alert keeps the same id on every read, for as long as it is raised. |
| `conversations[].dismissedAlerts[].kind` | string | Yes | What raised it. Today `escalation` (the person is pushing to get out of the automated conversation) or `sentiment` (their words read negative; not raised while `escalation` is). Treat this as an open set: more kinds will be added, and a client should render one it does not recognise from `label` and `detail` rather than drop it. |
| `conversations[].dismissedAlerts[].label` | string | Yes | A short name for the alert, such as `Asking for a person`. |
| `conversations[].dismissedAlerts[].detail` | string | Yes | One sentence on what was observed. On a call it names what drove the alert, such as how many times the caller asked for a person. A chat carries the general wording. |
| `conversations[].dismissedAlerts[].reason` | string | Yes | What the supervisor wrote when dismissing it. |
| `conversations[].dismissedAlerts[].dismissedBy` | string | No | The id of the user who dismissed it. Absent when it was not recorded. |
| `conversations[].dismissedAlerts[].dismissedAt` | string (date-time) | Yes | — |
| `conversations[].assignment` | object | No | Which supervisor has taken this conversation over, and since when. Absent when nobody has. |
| `conversations[].assignment.heldBy` | string | Yes | The id of the user who holds it. |
| `conversations[].assignment.heldByName` | string \| null | No | Their display name, or null when it could not be resolved. Left out when the board could not read who has the conversation. |
| `conversations[].assignment.joinedAt` | string (date-time) | Yes | — |
| `conversations[].assignment.takenOverAt` | string (date-time) | No | When the supervisor took the call over: the agent is gone and the caller is talking to them. Absent before that. |
| `conversations[].presence` | object | No | Who has the conversation open on the Supervisor board. At most one supervisor monitors it, and only they may coach, join, listen, or act on its alerts and held replies; everyone else who has it open is viewing it, and may read it but not act on it. Absent when nobody has it open. |
| `conversations[].presence.monitor` | object | No | The supervisor monitoring it. Absent when nobody is. |
| `conversations[].presence.monitor.userId` | string | Yes | — |
| `conversations[].presence.monitor.name` | string \| null | Yes | Null when it could not be resolved. |
| `conversations[].presence.monitor.since` | string (date-time) | Yes | — |
| `conversations[].presence.monitor.listening` | boolean | Yes | Whether they are listening to the call's audio. |
| `conversations[].presence.viewers` | array of object | Yes | — |
| `conversations[].presence.viewers[].userId` | string | Yes | — |
| `conversations[].presence.viewers[].name` | string \| null | Yes | — |
| `conversations[].heldReply` | object | No | A chat reply of the agent's held for review, which the visitor has not seen yet. Absent when none is. The reply itself arrives on the conversation's transcript stream as a `held_reply` event. |
| `conversations[].heldReply.holdId` | string | Yes | — |
| `conversations[].heldReply.deadline` | string (date-time) | Yes | When the reply sends itself, as written. |
| `partial` | boolean | No | True when part of the platform could not be read, so the list may be missing conversations. Treat this as "some conversations are not shown", which is a different fact from an empty list. It covers either channel — the flag does not say which half was short, because the only sound response is the same either way. |

### 400 — The request was malformed. `error` names the reason, such as `invalid_agentId`, `invalid_version`, `invalid_slot`, or `invalid_prompt`.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `error` | string | No | A stable machine-readable code. |
| `message` | string | No | A human-readable explanation, safe to show to an end user. Present on errors an operator can act on; browser clients display this field in preference to a generic fallback. |
| `limit` | integer | No | Present on some errors (e.g. `prompt_too_long`) that reject a value against a fixed bound: the bound itself. |
| `length` | integer | No | Present on some errors (e.g. `prompt_too_long`) that reject a value against a fixed bound: the measured size of the rejected value. |
| `reason` | string | No | Present on some errors (e.g. `invalid_avatar_image`) that have more than one cause: which one applied. |

### 401 — Missing, malformed, or expired bearer token.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `error` | string | No | A stable machine-readable code. |
| `message` | string | No | A human-readable explanation, safe to show to an end user. Present on errors an operator can act on; browser clients display this field in preference to a generic fallback. |
| `limit` | integer | No | Present on some errors (e.g. `prompt_too_long`) that reject a value against a fixed bound: the bound itself. |
| `length` | integer | No | Present on some errors (e.g. `prompt_too_long`) that reject a value against a fixed bound: the measured size of the rejected value. |
| `reason` | string | No | Present on some errors (e.g. `invalid_avatar_image`) that have more than one cause: which one applied. |
