# List chat widgets (/docs/api/voice-agents/chat-widgets/listChatWidgets)

> 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/chat-widgets`

List chat widgets

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `widgets` | array of object | No | — |
| `widgets[].id` | string | Yes | — |
| `widgets[].publicKey` | string | Yes | The key that goes in the embed snippet, minted on create and never changed. **Not a secret** — it is in your page source. It identifies the widget; `allowedOrigins` and the per-widget rate limits are what authorize a request. |
| `widgets[].createdAt` | string (date-time) | Yes | — |
| `widgets[].updatedAt` | string (date-time) | Yes | — |
| `widgets[].name` | string | Yes | The widget's name inside Tandem: how it is labelled in the dashboard. Visitors never see it — the chat header shows `displayName`, or this name when there is none. |
| `widgets[].displayName` | string | No | The bot name a visitor reads in the chat header. Omitted, the header shows `name`. Separate from `name` so a persona can be shared: several widgets may use the same display name while each keeps a distinct `name` you can tell apart in the dashboard. It is not required to be unique. |
| `widgets[].agentId` | string | No | The voice agent that answers this chat. Its system prompt, tools, knowledge base, skills, and safety settings all apply. Voice-only settings do not: nothing is synthesized or transcribed, and the telephony built-ins (DTMF capture, recording control, warm transfer) are unavailable in a chat. Supply either this or `workflowId`, never both and never neither. Neither is marked required because exactly one of the two is, and that is a rule this schema cannot express; a request naming both or naming neither is rejected with `invalid_target`. |
| `widgets[].workflowId` | string | No | The workflow that answers this chat, instead of an agent. A scripted conversation rather than a model deciding what to say: the workflow starts on the visitor's first message and a **Send Chat Message** node writes into their panel, optionally waiting for a typed reply. The widget's own `greeting` still greets, so nothing about the embed snippet changes. The workflow must have a published version — unlike an agent, an unpublished one cannot answer at all, so it is rejected with `workflow_not_published` rather than accepted. Voice nodes do not work here: **Play Message**, the recording nodes, **Disconnect** and the transfer nodes all need a phone call. The conversation ends when the workflow runs out of nodes. |
| `widgets[].description` | string | No | One line under the bot name. |
| `widgets[].avatarUrl` | string | No | An `https://` image URL. `http://` and `data:` URLs are rejected — the first is blocked as mixed content on every site the allowlist permits, and the second would put unbounded bytes in every visitor's page. Omitted, the widget draws the initials of the name in the header. Header only. It does not fill the launcher bubble — that is `launcherIcon`. |
| `widgets[].showAvatar` | boolean | No | Whether the chat header shows an image at all. Set to `false` and the header is the bot name alone — no picture and no initials disc. Different from leaving `avatarUrl` empty, which draws the initials. |
| `widgets[].position` | enum: `bottom-right` \| `bottom-left` | No | Which corner of the visitor's screen the launcher, and the panel above it, sits in. |
| `widgets[].launcherIcon` | enum: `bubble` \| `message` \| `custom` | No | The glyph on the launcher bubble. `bubble` and `message` are drawn by the widget itself; `custom` renders `launcherIconUrl` instead. `custom` with no usable `launcherIconUrl` falls back to `bubble` rather than rendering a broken image. |
| `widgets[].launcherIconUrl` | string | No | The launcher's image when `launcherIcon` is `custom`, and ignored otherwise. Same rule as `avatarUrl`: `https://` only, no `data:` URLs. |
| `widgets[].brandColor` | string | No | Your brand color as a six-digit hex, e.g. `#16a34a`. It fills the chat header, the launcher bubble, the send button, and the visitor's own messages. The text color on top of it is not configurable — the widget picks black or white per color, whichever is readable, so a pale brand color cannot produce a header nobody can read. Six digits only: no shorthand (`#4f5`), no `rgb()`, and no color keyword. Omitted, the widget uses its own default. |
| `widgets[].greeting` | string | No | The first message a visitor sees. Omitted, the agent's own greeting is used — which is usually written for a phone call. |
| `widgets[].allowedOrigins` | array of string | No | The websites allowed to open this widget. This is the widget's authorization, not the public key — the key is readable by anyone who views your page source, and what they cannot do is make a browser send someone else's `Origin`. An empty list allows nothing. A widget saved with no origins does not work anywhere, which you notice immediately; the alternative — empty meaning "any site" — is a widget that works in testing and lets anyone on the internet spend your model and tool budget. |

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