> 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.

# Twilio

Stream a Twilio call to Noesis with a Media Stream and a stream token, and receive the answers during the call through a relay.

Twilio Media Streams send a call's audio to a WebSocket. Noesis reads Twilio's messages as they are, and the defaults (μ-law, 8 kHz) match.

Twilio cannot send an `Authorization` header, so you pass a [stream token](https://platform.detectif.ai/docs/noesis/authentication/#stream-tokens) as a `<Parameter>` instead. Twilio does not pass messages from the stream back to you, so to act on the answers during the call, stream through a relay on your server (step 3).

## 1. Stream the call to Noesis

Add a `<Stream>` inside `<Start>`, so the call carries on while its audio is streamed. `track="both_tracks"` lets Noesis hear when your agent speaks, and the call ID defaults to the CallSid.

```xml
<Response>
  <Start>
    <Stream url="wss://api.noesis.detectif.ai/v1/detect/stream" track="both_tracks">
      <Parameter name="token" value="ntk_..."/>
    </Stream>
  </Start>
  <!-- your existing call flow continues here -->
</Response>
```

To not record the call, add `<Parameter name="record" value="false"/>`. See [Data and recording](https://platform.detectif.ai/docs/noesis/data-and-recording/).

## 2. Create a token for each call

A token opens one call and expires after 5 minutes, so create it in your voice webhook, right before you return the TwiML. This Flask webhook uses the Twilio helper library (`pip install flask requests twilio`):

```python
# voice.py: Twilio voice webhook that streams each call to Noesis
import os

import requests
from flask import Flask
from twilio.twiml.voice_response import Start, VoiceResponse

app = Flask(__name__)

@app.post("/voice")
def voice():
    response = VoiceResponse()
    try:
        r = requests.post(
            "https://api.noesis.detectif.ai/v1/detect/stream-tokens",
            headers={"Authorization": f"Bearer {os.environ['DETECTIF_API_KEY']}"},
            json={},
            timeout=5,
        )
        r.raise_for_status()
        start = Start()
        stream = start.stream(url="wss://api.noesis.detectif.ai/v1/detect/stream",
                              track="both_tracks")
        stream.parameter(name="token", value=r.json()["token"])
        response.append(start)
    except requests.RequestException:
        pass                                        # never block the call on Noesis
    response.say("Your existing call flow continues here.")
    return str(response), 200, {"Content-Type": "text/xml"}
```

## 3. Receive the answers during the call

With the stream pointed straight at Noesis, the answers go back to Twilio, which ignores them. To act on them, point the `<Stream>` at a relay on your server instead. The relay forwards Twilio's messages to Noesis unchanged, with your API key in the header, so it needs no token:

```xml
<Response>
  <Start>
    <Stream url="wss://relay.example.com/twilio" track="both_tracks"/>
  </Start>
  <!-- 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
# twilio_relay.py: Twilio Media 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 the CallSid

async def handle(twilio):
    call = NoesisCall(on_answer)                    # Twilio's format is the default
    await call.open()
    try:
        async for message in twilio:                # Twilio'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())
```

Act on the latest `seq` for each CallSid. For example, update the live call with Twilio's REST API when the answer is `voicemail`. To not record calls through the relay, create the call with `NoesisCall(on_answer, record="false")`.
