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

# WebSocket client

Connect to Noesis from your own code in Python or Node.js, and the relay helper the telephony guides use.

Use these clients when your own code has the call's audio, for example a media server, a softphone or a voice bot. Each opens one socket per call, sends the callee's audio, and passes every answer to your code.

## Python

Needs `pip install websockets` (version 14 or later).

```python
# noesis_client.py: one Noesis socket per call
import asyncio, json, os

import websockets

STREAM = "wss://api.noesis.detectif.ai/v1/detect/stream"
HEADERS = {"Authorization": f"Bearer {os.environ['DETECTIF_API_KEY']}"}

async def analyse_call(frames, call_id, on_answer, encoding="mulaw", sample_rate=8000):
    """frames: an async iterator of the callee's audio, 20 to 100 ms per frame."""
    url = f"{STREAM}?encoding={encoding}&sample_rate={sample_rate}&call_id={call_id}"
    try:
        ws = await websockets.connect(url, additional_headers=HEADERS)
    except Exception:
        return                                      # never block the call on Noesis

    async def answers():
        try:
            async for raw in ws:
                event = json.loads(raw)
                if event["type"] in ("classification", "summary"):
                    await on_answer(event)
        except websockets.ConnectionClosed:
            pass

    reader = asyncio.create_task(answers())
    try:
        async for frame in frames:
            await ws.send(frame)
        await ws.send(json.dumps({"type": "end"}))
    except websockets.ConnectionClosed:
        pass                                        # Noesis closed early: the call carries on
    try:
        await asyncio.wait_for(reader, timeout=10)  # the summary arrives after "end"
    except asyncio.TimeoutError:
        await ws.close()
```

## Node.js

Needs Node.js 18 or later and `npm install ws`.

```javascript
// noesis-client.mjs: one Noesis socket per call
import WebSocket from "ws";

const STREAM = "wss://api.noesis.detectif.ai/v1/detect/stream";

export function analyseCall({ callId, onAnswer, encoding = "mulaw", sampleRate = 8000 }) {
  const url = `${STREAM}?encoding=${encoding}&sample_rate=${sampleRate}&call_id=${encodeURIComponent(callId)}`;
  const ws = new WebSocket(url, { headers: { Authorization: `Bearer ${process.env.DETECTIF_API_KEY}` } });
  ws.on("error", () => {});                         // never block the call on Noesis
  ws.on("message", (raw) => {
    const event = JSON.parse(raw);
    if (event.type === "classification" || event.type === "summary") onAnswer(event);
  });
  return {
    send(frame) { if (ws.readyState === WebSocket.OPEN) ws.send(frame); },
    end() { if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: "end" })); },
  };
}
```

Use it from your media handler:

```javascript
const noesis = analyseCall({ callId, onAnswer: (e) => console.log(e.label) });
// for each frame of the callee's audio:
noesis.send(frame);
// when the call ends:
noesis.end();
```

## Browsers

A browser cannot set the `Authorization` header on a WebSocket, and must never hold an API key. Have your server create a [stream token](https://platform.detectif.ai/docs/noesis/authentication/#stream-tokens) for the call, and open the socket with it:

```javascript
const ws = new WebSocket(`wss://api.noesis.detectif.ai/v1/detect/stream?token=${token}&encoding=pcm_s16le&sample_rate=16000`);
```

## Relay helper

Most telephony platforms send a call's audio to a WebSocket or TCP server that you run. The [Telnyx](https://platform.detectif.ai/docs/noesis/telnyx/), [Exotel](https://platform.detectif.ai/docs/noesis/exotel/), [Vonage](https://platform.detectif.ai/docs/noesis/vonage/), [FreeSWITCH](https://platform.detectif.ai/docs/noesis/freeswitch/), [Asterisk](https://platform.detectif.ai/docs/noesis/asterisk/), [Twilio](https://platform.detectif.ai/docs/noesis/twilio/) and [Plivo](https://platform.detectif.ai/docs/noesis/plivo/) guides run a small relay: it receives the platform's stream, forwards the callee's audio to Noesis, and passes the answers to your code. They share this helper:

```python
# noesis_relay.py: forward one call's audio to Noesis and pass its answers to your code
import asyncio, json, os
from urllib.parse import urlencode

import websockets

STREAM = "wss://api.noesis.detectif.ai/v1/detect/stream"
HEADERS = {"Authorization": f"Bearer {os.environ['DETECTIF_API_KEY']}"}

class NoesisCall:
    """One call. Never raises: if Noesis is unreachable or closes, the call carries on."""

    def __init__(self, on_answer, **params):
        self.on_answer = on_answer
        self.query = urlencode({k: v for k, v in params.items() if v is not None})
        self.ws = self.reader = None

    async def open(self):
        try:
            self.ws = await websockets.connect(f"{STREAM}?{self.query}", additional_headers=HEADERS)
        except Exception:
            self.ws = None
            return
        self.reader = asyncio.create_task(self._read())

    async def _read(self):
        try:
            async for raw in self.ws:
                event = json.loads(raw)
                if event["type"] in ("classification", "summary", "error"):
                    await self.on_answer(event)
        except websockets.ConnectionClosed:
            pass

    async def send(self, data):
        """Binary audio, or a JSON message as text."""
        if self.ws is None:
            return
        try:
            await self.ws.send(data)
        except websockets.ConnectionClosed:
            self.ws = None

    async def close(self):
        await self.send(json.dumps({"type": "end"}))
        if self.reader is not None:
            try:
                await asyncio.wait_for(self.reader, timeout=10)   # the summary follows "end"
            except asyncio.TimeoutError:
                pass
        if self.ws is not None:
            await self.ws.close()
```

`on_answer` receives each `classification`, the `summary` and any `error` event. The relay must be reachable from the platform over `wss://` (or TCP for Asterisk), for example behind your load balancer or a TLS proxy.
