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

# Stream Calls

> Talk to your Pearl from your own app or website, its audio streamed over a WebSocket.

A stream call gives your app a live, speech-to-speech conversation with your Pearl: you stream the end user's voice in and play Pearl's voice back as it speaks. It is the same Pearl you integrate with your phone numbers, with the same flow, call history, webhooks, recordings and billing. Only the connection differs: your app starts the call through the API, then connects to Pearl over a WebSocket, with no phone line involved.

## Before you start

To make your first stream call, you need:

* A [voice Pearl](https://developers.nlpearl.ai/pages/getting_started.md#2-create-your-agent-with-pearlvibe) that is **active**. The call runs its [published](https://developers.nlpearl.ai/pages/getting_started.md#5-publish-your-agent) version.
* Its **Pearl ID**: on your Pearl's overview page, in the **Settings** popover.
* An API key, or an OAuth app with the **`pearls:stream`** scope (**Make stream calls through the API** in the platform). See [API Authorization](https://developers.nlpearl.ai/api-reference/authorization).
* One of the four audio formats, the same in both directions:

| `audioFormat` | Audio | Use it for |
| - | - | - |
| `opus_24000` | 24 kHz Opus | HD at a fraction of the bandwidth of `pcm_24000`. Websites and apps |
| `opus_8000` | 8 kHz Opus | Phone quality at less bandwidth than `ulaw_8000` |
| `ulaw_8000` (default) | 8 kHz μ-law | Phone quality. The format of Twilio and most phone systems |
| `pcm_24000` | 24 kHz 16-bit PCM | HD with no codec, at the highest bandwidth |

> [!TIP]
>
> **We recommend Opus.** It sounds nearly the same for a fraction of the bandwidth, so calls hold up better on mobile and slow networks.

## Connect

**1. Create the token**

From your backend, call [Create Stream Token](https://developers.nlpearl.ai/api-reference/v2/stream/create-token.md). It creates the call and returns the `url` to open and a `token` valid for 60 seconds.

```bash theme={null}
curl -X POST "https://api.nlpearl.ai/v2/Pearl/<pearlId>/Stream/Token" \
  -H "Authorization: Bearer <AccountId>:<SecretKey>" \
  -H "Content-Type: application/json" \
  -d '{ "endUserId": "crm-77812", "audioFormat": "opus_24000" }'
```

`endUserId` is [your own id for the person on the call](https://developers.nlpearl.ai/api-reference/v2/stream/create-token.md#body-end-user-id-one-of-0), such as a customer id: send the same value for the same person every time. It is optional: without it, Pearl does not remember the person.

**2. Open the WebSocket**

Open the `url` with the token, within 60 seconds. Pearl sends `ready`, then you stream.

```javascript theme={null}
// call: the Create Stream Token response, handed over by your backend
const ws = new WebSocket(`${call.url}?token=${call.token}`);
```

> [!WARNING]
>
> Your API key stays on your backend. A browser or an app gets only the `url` and the `token`, which open this one call, once.

## Stream audio

* **Audio is binary**, in both directions: raw bytes in the call's format, with no base64 and no JSON. On an Opus call, each message is one Opus packet.
* **Send one 20 ms frame every 20 ms**, in real time, for the whole call. When the end user is quiet or muted, send silence, not nothing.
* **Pearl sends one 20 ms frame every 20 ms** too, with silence while it listens. Play the frames through a small jitter buffer of 60 to 100 ms.
* **Interruptions need no code.** When the end user talks over Pearl, it stops speaking and the next frames are already silence: there is nothing to clear on your side.

## Events

Text messages are JSON events, such as `{"type":"hangup"}`, in both directions:

| Event | Sent by | When |
| - | - | - |
| `ready` | Pearl | Once, right after you connect |
| `transfer` | Pearl | The flow [transferred](https://developers.nlpearl.ai/pages/transfer_call.md) the call. It carries the target and the data collected so far; carrying the call on is up to you |
| `ended` | Pearl | The call is over, with the `reason`. Pearl then closes the connection |
| `pong` | Pearl | The answer to your `ping` |
| `hangup` | You | Ends the call. Pearl answers with `ended`. From your backend, [Hang Up Call](https://developers.nlpearl.ai/api-reference/v2/call/hangup-call.md) ends it too |
| `ping` | You | Optional, to time the round trip |

## After the call

The call appears in your call history and [webhooks](https://developers.nlpearl.ai/pages/webhooks.md) like any other call of your Pearl, with `channel: "stream"` and your `endUserId` where the end user's phone number would be (empty without one).

## Errors and limits

The cases you are most likely to meet. [Stream WebSocket](https://developers.nlpearl.ai/api-reference/v2/stream/websocket.md) lists every end reason and limit.

| What happens | Why | What to do |
| - | - | - |
| The create is refused with `No agent is available…` | Every one of your Pearl's [agents](https://developers.nlpearl.ai/pages/agents.md) is on a call, phone calls included | Retry later, or give your Pearl more agents |
| The connection is refused with `401` | The token expired, was already used, or is missing | Create a new token and connect right away |
| The call ends with `no_audio` | 60 seconds without a message from you | Keep sending silence frames |
| The call ends with `max_duration` | Your Pearl's Max Call Duration was reached | Set a longer duration on your Pearl |

## Examples

> [card] [Quickstart](https://developers.nlpearl.ai/pages/stream/quickstart.md)
>
> Talk to your Pearl from your terminal, with your microphone.

> [card] [Browser App](https://developers.nlpearl.ai/pages/stream/browser.md)
>
> A web page with the microphone and Pearl's voice.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.