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

# Live-listen SDK

> Embed live call monitoring in your own tool.

The live-listen SDK is a browser script that lets you subscribe to a live conversation's audio and transcript from your own page. It connects as a read-only observer: you hear the call and receive transcript events, but you cannot speak and you are hidden from the other participants.

Use it to build custom monitoring, supervisor, or QA tools outside the Anyreach dashboard. For the built-in monitoring experience, see [Live monitoring](/conversations/live-monitoring).

## How it works

The SDK is served as JavaScript at `/public/listen-sdk`. When you call `AnyReach.listen(...)`, it injects a hidden iframe into your page and brokers all audio and transcript traffic through it over `postMessage`. You never handle the LiveKit connection directly.

```
your page  ──AnyReach.listen()──►  hidden iframe (/public/listen)  ──►  LiveKit room
   ▲                                       │
   └────── stateChange / transcript ───────┘
```

## Load the script

Add the script tag to your page. Point it at your Anyreach host.

```html theme={null}
<script src="https://app.anyreach.ai/public/listen-sdk" async></script>
```

This registers `AnyReach.listen` on `window`.

## Get a token

`AnyReach.listen` requires a `url` and `token`, which you mint from the API.

```
POST https://api.anyreach.ai/core/conversations/{conversation_id}/listener-token
```

This endpoint requires the `conversations:read_sensitive` or `conversations:manage` scope. Authenticate with `Authorization: Bearer <token>`; user PATs (`pat_`) also need `X-Anyreach-Org: <organization_id>`.

The conversation must be in progress. If it is not active, the endpoint returns `409`. If the conversation has no associated LiveKit room, it returns `404`.

```bash theme={null}
curl -X POST \
  https://api.anyreach.ai/core/conversations/{conversation_id}/listener-token \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json"
```

The response contains everything you pass to the SDK:

| Field        | Type                   | Description                                   |
| ------------ | ---------------------- | --------------------------------------------- |
| `url`        | string                 | LiveKit server URL. Pass as `url`.            |
| `token`      | string                 | Subscribe-only listener JWT. Pass as `token`. |
| `room_name`  | string                 | The LiveKit room the conversation is in.      |
| `expires_at` | string (ISO 8601, UTC) | When the listener token expires.              |

<Warning>
  Mint the token from your backend, not the browser. The listener-token scopes grant access to sensitive conversation audio, so the credential you authenticate with should never reach the client.
</Warning>

## Start a session

Wire your event handlers first, then call `connect()`.

<Steps>
  <Step title="Create the session">
    Pass the `url` and `token` from the listener-token response.

    ```js theme={null}
    const session = AnyReach.listen({ url, token });
    ```
  </Step>

  <Step title="Attach handlers">
    Register handlers before connecting so you do not miss early events.

    ```js theme={null}
    session.on("stateChange", (state) => console.log(state));
    session.on("transcript", (t) => console.log(t.role, t.text));
    session.on("error", (e) => console.error(e.message));
    ```
  </Step>

  <Step title="Connect">
    `connect()` returns a promise that resolves once the listener is subscribed. It rejects on error or after a 15-second timeout.

    ```js theme={null}
    await session.connect();
    ```
  </Step>

  <Step title="Enable audio">
    Browser autoplay policy may suspend audio until a user gesture. Call `startAudio()` from a click handler.

    ```js theme={null}
    playButton.addEventListener("click", () => session.startAudio());
    ```
  </Step>
</Steps>

## Session API

`AnyReach.listen({ url, token })` returns a session object with these methods.

| Method           | Returns         | Description                                                                                                                                                      |
| ---------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connect()`      | `Promise<void>` | Connects the listener. Resolves when subscribed, rejects on error or after a 15-second timeout. Calling it again while connecting returns the in-flight promise. |
| `startAudio()`   | `Promise<void>` | Resumes audio playback. Call from a user-gesture handler to satisfy browser autoplay policy.                                                                     |
| `mute()`         | `void`          | Mutes the listener's audio output.                                                                                                                               |
| `unmute()`       | `void`          | Unmutes the listener's audio output.                                                                                                                             |
| `isMuted()`      | `boolean`       | Returns the current mute state.                                                                                                                                  |
| `getState()`     | `string`        | Returns the current connection state.                                                                                                                            |
| `end()`          | `Promise<void>` | Ends the session and removes the hidden iframe. Resolves once teardown completes (forced after 3 seconds).                                                       |
| `on(event, fn)`  | session         | Registers an event handler. Returns the session for chaining.                                                                                                    |
| `off(event, fn)` | session         | Removes a previously registered handler. Returns the session for chaining.                                                                                       |

<Note>
  `mute()` and `unmute()` control your local audio output only. They do not affect what the agent or caller hear.
</Note>

## Events

Subscribe with `session.on(event, handler)`.

| Event               | Payload                     | Description                                                                                                     |
| ------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `stateChange`       | `string`                    | Fires when the connection state changes.                                                                        |
| `transcript`        | `{ id, role, text, final }` | A transcript segment. `final` is `false` for in-progress (interim) text and `true` once the segment is settled. |
| `agentDisconnected` | `{ participantIdentity }`   | The agent participant left the room.                                                                            |
| `error`             | `{ message }`               | An error occurred. Also rejects an in-flight `connect()`.                                                       |
| `ended`             | —                           | The session ended and the iframe was torn down.                                                                 |

<Note>
  `getState()` and the `stateChange` event report `idle`, `connecting`, `connected`, or `disconnected`.
</Note>

## Full example

```html theme={null}
<script src="https://app.anyreach.ai/public/listen-sdk" async></script>
<button id="play">Listen</button>

<script>
  async function startListening(conversationId) {
    // Fetch the token from your backend, which holds the API credential.
    const { url, token } = await fetch(
      `/api/listener-token?conversation=${conversationId}`
    ).then((r) => r.json());

    const session = AnyReach.listen({ url, token });

    session.on("stateChange", (state) => {
      console.log("state:", state);
    });
    session.on("transcript", ({ role, text, final }) => {
      if (final) console.log(`${role}: ${text}`);
    });
    session.on("agentDisconnected", () => {
      console.log("agent left");
    });
    session.on("error", ({ message }) => {
      console.error("listen error:", message);
    });
    session.on("ended", () => {
      console.log("session ended");
    });

    await session.connect();

    // Autoplay policy: resume audio from a user gesture.
    document
      .getElementById("play")
      .addEventListener("click", () => session.startAudio());
  }
</script>
```

## Next steps

<CardGroup cols={2}>
  <Card title="Live monitoring" icon="headphones" href="/conversations/live-monitoring">
    Listen to in-progress conversations from the Anyreach dashboard.
  </Card>

  <Card title="Agent Assist overview" icon="bolt" href="/agent-assist/overview">
    See the full set of real-time assist capabilities.
  </Card>
</CardGroup>
