> For AI agents: the documentation index is at https://platform.detectif.ai/llms.txt. Every page is also available as Markdown by adding `.md` to its path.

# Plivo

Stream a Plivo call to Noesis with the Stream XML element and a stream token, and receive the answers through a relay.

Plivo's `<Stream>` element sends a call's audio to a WebSocket. Noesis reads Plivo's media messages as they are. Plivo cannot send an `Authorization` header to Noesis, so put a [stream token](https://platform.detectif.ai/docs/noesis/authentication/#stream-tokens) in the URL.

## 1. Stream the call to Noesis

Ask Plivo for μ-law at 8 kHz and set the same format on the URL. In XML, write each `&` in the URL as `&amp;`:

```xml
<Response>
  <Stream bidirectional="false" audioTrack="inbound" contentType="audio/x-mulaw;rate=8000">wss://api.noesis.detectif.ai/v1/detect/stream?token=ntk_...&amp;encoding=mulaw&amp;sample_rate=8000</Stream>
  <!-- your existing call flow continues here -->
</Response>
```

Create the token in your answer webhook, right before you return the XML, as in the [Twilio guide](https://platform.detectif.ai/docs/noesis/twilio/#2-create-a-token-for-each-call). To not record the call, add `&amp;record=false` to the URL.

## 2. Receive the answers during the call

Plivo does not pass messages from the stream back to you. To act on the answers, point the stream at a relay on your server, which forwards Plivo's messages to Noesis unchanged and receives the answers:

```xml
<Response>
  <Stream bidirectional="false" audioTrack="inbound" contentType="audio/x-mulaw;rate=8000">wss://relay.example.com/plivo</Stream>
  <!-- your existing call flow continues here -->
</Response>
```

The relay uses `noesis_relay.py` from the [relay helper](https://platform.detectif.ai/docs/noesis/websocket-client/#relay-helper):

```python
# plivo_relay.py: Plivo Stream -> your server -> Noesis
import asyncio

import websockets

from noesis_relay import NoesisCall

async def on_answer(event):
    if event["type"] == "classification":
        print(event["call_id"], event["label"])     # your own logic; call_id is Plivo's call ID

async def handle(plivo):
    call = NoesisCall(on_answer, encoding="mulaw", sample_rate=8000)
    await call.open()
    try:
        async for message in plivo:                 # Plivo's JSON messages, unchanged
            await call.send(message)
    except websockets.ConnectionClosed:
        pass
    await call.close()

async def main():
    async with websockets.serve(handle, "0.0.0.0", 8080):
        await asyncio.Future()                      # run until stopped

asyncio.run(main())
```

If you choose 16-bit PCM in `contentType` (`audio/x-l16;rate=8000` or `audio/x-l16;rate=16000`), set `encoding="pcm_s16le"` and the same `sample_rate`.
