# Stream a call's audio to your WebSocket

`POST https://api.morevoice.ai/v1/calls/{id}/streams`

Starts a media stream on a live call in `fork` mode: a one-way copy of the caller (`inbound`), what the caller hears (`outbound`), or both, sent to your `wss://` URL as Twilio Media Streams messages (`connected`, `start`, `media`, `stop`). The call goes on exactly as before: if your server is slow, frames older than 2 s are dropped (`frames_dropped`); if it disconnects, the stream reconnects (3 tries) and otherwise gives up (`failed`) without touching the call. The upgrade request is signed with your signing secret over its path and query (`webhook-id` is the stream's ID). The stream stops when the call ends or with DELETE. At most 2 streams per call.

Authenticate with a secret API key: `Authorization: Bearer $MOREVOICE_API_KEY`.

Operation ID: `calls_create_stream`.

## Parameters

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | A call ID (`call_…`). |

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `MoreVoice-Version` | `string` | no | The API version to use for this request. Defaults to the version the API key is pinned to. |
| `Idempotency-Key` | `string` | no | A unique key (for example a UUID) that makes this request safe to retry: for 24 hours, a retry with the same key and parameters returns the first response instead of acting twice. |

## Request body

Required, `application/json`.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | `string` | yes | Your WebSocket URL (`wss://`). The upgrade is signed with your signing secret (`webhook-id`, `webhook-timestamp`, `webhook-signature` over the path and query). |
| `custom_parameters` | `Record<string, string>` | no | Up to 20 string key/values the receiver gets in `start.customParameters` (as Twilio's stream parameters). |
| `format` | `"mulaw_8000" \| "l16_16000" \| "l16_8000"` | no | `mulaw_8000` (the default): G.711 μ-law at 8 kHz, Twilio's; `l16_16000` / `l16_8000`: signed 16-bit PCM, little-endian. |
| `mode` | `"fork"` | no | `fork` (the default): a one-way copy; your server's messages are ignored. |
| `tracks` | `"inbound" \| "outbound" \| "both"` | no | `inbound`: the caller; `outbound`: what the caller hears (the assistant, an agent, prompts); `both` (the default). |

Example:

```json
{
  "custom_parameters": {
    "crm_id": "42"
  },
  "format": "mulaw_8000",
  "tracks": "both",
  "url": "wss://media.example.com/streams"
}
```

## Responses

### 201 Created

The stream, connecting. Returns `MediaStream`.

| Field | Type | Description |
| --- | --- | --- |
| `id` | `string` | The stream's ID (`ms_…`): `streamSid` in the protocol's messages. |
| `object` | `"media_stream"` | — |
| `livemode` | `boolean` | `true` in live mode, `false` in test mode. |
| `call_id` | `string` | The call whose audio is streamed. |
| `mode` | `"fork"` | `fork`: a one-way copy of the call's audio; the call goes on as before. |
| `url` | `string` | The receiver's WebSocket URL. |
| `tracks` | `"inbound" \| "outbound" \| "both"` | `inbound`: the caller; `outbound`: what the caller hears (the assistant, an agent, prompts); `both`. |
| `format` | `"mulaw_8000" \| "l16_16000" \| "l16_8000"` | `mulaw_8000`: G.711 μ-law at 8 kHz (Twilio's); `l16_16000` / `l16_8000`: signed 16-bit PCM, little-endian. |
| `custom_parameters` | `Record<string, string>` | — |
| `status` | `"connecting" \| "streaming" \| "reconnecting" \| "stopped" \| "failed"` | `connecting` (and `reconnecting` after the receiver dropped), `streaming`, `stopped`, or `failed` (the receiver could not be reached; the call went on). |
| `frames_sent` | `integer` | 20 ms frames sent so far (all tracks). |
| `frames_dropped` | `integer` | Frames dropped because the receiver did not keep up (at most 2 s of audio waits per track). |
| `ended_reason` | `string \| null` | Why it ended: `call_ended`, `stopped`, `receiver_closed` or `connect_failed`; null while it runs. |
| `created` | `string` | An ISO-8601 timestamp in UTC. |

Example:

```json
{
  "call_id": "call_8tRPaZp5hLMbrGqdJ9AmNa",
  "created": "2026-11-03T09:14:22.000Z",
  "custom_parameters": {
    "crm_id": "42"
  },
  "ended_reason": null,
  "format": "mulaw_8000",
  "frames_dropped": 0,
  "frames_sent": 0,
  "id": "ms_4fG7hJ2kL9mN3pQ6rS8tUv",
  "livemode": true,
  "mode": "fork",
  "object": "media_stream",
  "status": "connecting",
  "tracks": "both",
  "url": "wss://media.example.com/streams"
}
```

### 400 Bad Request

The request is invalid: a parameter is missing, malformed or unknown, or the version header is unknown. Returns `ErrorEnvelope`.

### 401 Unauthorized

No valid API key was sent. Returns `ErrorEnvelope`.

### 403 Forbidden

The key may not do this (a missing scope, a plan limit, or a compliance block). Returns `ErrorEnvelope`.

### 404 Not Found

No object with this ID exists in this organisation and mode. Returns `ErrorEnvelope`.

### 409 Conflict

The request conflicts with the object's state, or the Idempotency-Key was reused with other parameters. Returns `ErrorEnvelope`.

### 429 Too Many Requests

Too many requests, or no call capacity right now. Retry after the Retry-After delay. Returns `ErrorEnvelope`.

### 500 Internal Server Error

Something went wrong on MoreVoice's side. Retry with the same Idempotency-Key. Returns `ErrorEnvelope`.

## Code samples

**TypeScript**

```ts
import MoreVoice from "@morevoice/sdk";

const mv = new MoreVoice(); // MOREVOICE_API_KEY from the environment

const mediaStream = await mv.calls.createStream("call_7Hk2Lm9Qp", {
	custom_parameters: {
		crm_id: "42",
	},
	format: "mulaw_8000",
	tracks: "both",
	url: "wss://media.example.com/streams",
});
console.log(mediaStream);
```

**Python**

```python
from morevoice import MoreVoice

client = MoreVoice()  # MOREVOICE_API_KEY from the environment

media_stream = client.calls.create_stream("call_7Hk2Lm9Qp", {
    "custom_parameters": {
        "crm_id": "42",
    },
    "format": "mulaw_8000",
    "tracks": "both",
    "url": "wss://media.example.com/streams",
})
print(media_stream)
```

**cURL**

```sh
curl -X POST https://api.morevoice.ai/v1/calls/call_7Hk2Lm9Qp/streams \
  -H "Authorization: Bearer $MOREVOICE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "custom_parameters": {
    "crm_id": "42"
  },
  "format": "mulaw_8000",
  "tracks": "both",
  "url": "wss://media.example.com/streams"
}'
```
