> For the full page index, fetch https://echonova.ai/llms.txt

# Live sessions with an agent

Start a voice or chat session with an agent from your own code, join it with the room token, and end it. Includes the limits, credits and how to play the agent's voice through your own avatar.

A live session is the same session the **Test** button on an agent runs in the
portal. You start it with the API, join it from your own app, talk or type to
the agent, and end it. The key needs the **Talk to agents** access.

## What you need

- An API key with **Talk to agents** (`agents:talk`). See [Give a key talk access](#give-a-key-talk-access).
- The agent id, as `GET /v1/agents` returns it.
- The `livekit-client` package in the app that joins the session.

## Give a key talk access

Under **Agent and tool access** on the key form, tick **Talk to agents**. A key with
only this access can start, read and end sessions. It cannot change agents.
Without it the API answers `403` with `This API key cannot talk to agents.`.

The key acts for the member who created it. That member must keep the
permission to view agents and the permission to manage the developer API.

## Start, join, end

1. Start the session with your key. The answer carries a room `token` and a `wsUrl`.
2. Join the room from the client with the token.
3. End the session when the user is done.

Start a voice session. Leave `channel` out for voice, or send `"chat"` for a text session.

```bash
curl -X POST "https://api.echonova.ai/v1/agents/{agentId}/sessions" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"channel":"voice"}'
```

The answer holds `id`, `agentId`, `channel`, `status`, `startedAt`, `endedAt`,
`token`, `wsUrl` and `greeting`. `greeting` is the agent's first message for a
chat session and `null` for voice. See
[Start a live session](/docs/reference/agents-start-session).

### Join a voice session

Open the microphone, connect with `wsUrl` and `token`, publish the microphone
and play the agent's audio track.

```javascript
import { Room, RoomEvent, createLocalAudioTrack } from 'livekit-client';

const mic = await createLocalAudioTrack();
const room = new Room({ adaptiveStream: true, dynacast: true });
const audio = new Audio();
audio.autoplay = true;

room.on(RoomEvent.TrackSubscribed, (track) => {
  if (track.kind === 'audio') {
    track.attach(audio);
    audio.play().catch(() => undefined);
  }
});

await room.connect(session.wsUrl, session.token);
await room.localParticipant.publishTrack(mic);
```

An agent track that is already in the room when you join is not announced
again. After you connect, attach the audio tracks that are already there:

```javascript
room.remoteParticipants.forEach((participant) =>
  participant.audioTrackPublications.forEach((publication) => {
    if (publication.track) publication.track.attach(audio);
  }),
);
```

### Join a chat session

A chat session is text only. Send the user's message on the topic `lk.chat`.
The agent answers on the topic `lk.transcription`, one text stream for each
turn. `echonova.status` carries short progress notes as JSON `{ "kind", "text" }`. It is optional: a client can ignore it.

```javascript
import { Room } from 'livekit-client';

const room = new Room();

room.registerTextStreamHandler('lk.transcription', async (reader) => {
  let text = '';
  for await (const chunk of reader) {
    text += chunk;
    // Show `text` as it grows.
  }
});

await room.connect(session.wsUrl, session.token);
await room.localParticipant.sendText('What are your opening hours?', {
  topic: 'lk.chat',
});
```

Show `greeting` from the start answer before the user types. The agent may also
send the same greeting as its first stream; skip that stream so it shows once.

### End the session

Leave the room, then end the session so the room closes and the conversation
is saved.

```javascript
await room.disconnect();
```

```bash
curl -X POST "https://api.echonova.ai/v1/agents/{agentId}/sessions/{sessionId}/end" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Ending a session that already ended answers `200` with its final status.
`GET /v1/agents/{agentId}/sessions/{sessionId}` returns the status at any time.
See [Get a live session](/docs/reference/agents-get-session) and
[End a live session](/docs/reference/agents-end-session).

## Keep the token on your side

- The `token` is sent once, in the start answer. The read and end calls never return it.
- Hand it only to the client that joins. Do not log it or store it.
- A token opens one session. To talk again, start a new session.
- Start sessions from your server, so the API key never reaches the browser.

A retried start opens a second session, so do not retry a start blindly.

## Limits and credits

- **Starts:** 10 a minute for each key. Past that the API answers `429`; wait `Retry-After` seconds.
- **The agent's own limits:** the agent's chat limits and call limits apply to sessions started here. A full agent answers `409` with `This agent has reached its call limit. Try again later.` for a voice start, or `This agent has reached its chat limit. Try again later.` for a chat start.
- **Credits:** a session uses workspace credits, as a test in the portal does. They do not count against the key's **Credit Limit**. A workspace with no credits answers `402` before the session starts, and nothing is charged.
- **Archived agents:** an archived agent answers `409`. Restore it first.

Every error text is on [Errors](/docs/developers/errors#agents).

## Use your own avatar

If your app already has an avatar, it can speak for the agent. Join the voice
session with the token, take the agent's audio track and play it through your
avatar. That is all. EchoNova sends no extra events for an avatar and does not
render the face. Your app draws it.

## Related

- [Errors](/docs/developers/errors)
- [Rate limits](/docs/developers/rate-limits)
- [Changelog](/docs/developers/changelog)
