Noesis

Create a key for Noesis, get a stream token, stream a recorded call over the WebSocket and read the answers.

Stream a recorded call to Noesis and watch the answers arrive, exactly as they would on a live call.

1. Create an API key for Noesis

Sign in at platform.detectif.ai, open API keys and create a key that is allowed to use Noesis: noesis:detect to analyse calls, and noesis:read to read sessions and usage. The key is shown once; keep it in an environment variable:

Shell
export DETECTIF_API_KEY="dtf_live_..."

2. Get a stream token

Code on your server can send the key in the Authorization header when it opens the WebSocket. Clients that cannot set headers, such as Twilio, use a stream token instead. This quickstart uses a token, so you see both steps:

Shell
curl -X POST https://api.noesis.detectif.ai/v1/detect/stream-tokens \
  -H "Authorization: Bearer $DETECTIF_API_KEY"
JSON
{"token": "ntk_4mZ0pQx7Lr2Vd9Kc1Ty6Wb3Ns8Hf5Ja0Ge2Ru7Xo4Il", "expires_at": "2026-10-19T10:05:00Z"}

A token opens one call and expires after 5 minutes. The scripts below create their own token each time they run.

3. Stream a recording

A recording streamed in real time behaves like a live call. Python needs pip install websockets (version 14 or later); Node.js 18 or later needs npm install ws. Both read a 16-bit PCM mono WAV file.

Python
# noesis_quickstart.py: stream a recording to Noesis as if it were a live call
import asyncio, json, os, sys, urllib.request, wave

import websockets

API = "https://api.noesis.detectif.ai"
STREAM = "wss://api.noesis.detectif.ai/v1/detect/stream"

def stream_token():
    request = urllib.request.Request(
        f"{API}/v1/detect/stream-tokens",
        data=b"{}",
        method="POST",
        headers={"Authorization": f"Bearer {os.environ['DETECTIF_API_KEY']}", "Content-Type": "application/json"},
    )
    with urllib.request.urlopen(request, timeout=30) as response:
        return json.load(response)["token"]

async def main(path):
    with wave.open(path) as w:                      # 16-bit PCM WAV, mono
        rate, pcm = w.getframerate(), w.readframes(w.getnframes())
    url = f"{STREAM}?token={stream_token()}&encoding=pcm_s16le&sample_rate={rate}&call_id=test-1"
    async with websockets.connect(url) as ws:

        async def events():
            try:
                async for raw in ws:
                    e = json.loads(raw)
                    if e["type"] == "classification":
                        print(f'{e["t"]:6.1f}s  {e["label"]}  ({e["category"]})')
                    elif e["type"] == "summary":
                        print("final:", e["label"])
                    elif e["type"] == "error":
                        print("error:", e)
            except websockets.ConnectionClosed as closed:
                print("closed:", closed.rcvd.code if closed.rcvd else "")

        reader = asyncio.create_task(events())
        step = rate * 2 // 50                       # 20 ms of audio per frame
        for i in range(0, len(pcm), step):
            await ws.send(pcm[i:i + step])
            await asyncio.sleep(0.02)               # real time, like a live call
        await ws.send(json.dumps({"type": "end"}))
        await reader                                # ends when Noesis closes the socket

asyncio.run(main(sys.argv[1]))

4. Run it and read the answers

Run python noesis_quickstart.py call.wav or node noesis-quickstart.mjs call.wav. The output looks like this:

Text
   3.2s  ivr  (machine)
  13.0s  hold  (machine)
  23.4s  human  (human)
final: human

If the socket closes with an error, both scripts print the error event and the close code; see Errors.

  • Each classification event is an answer: label says who or what answered, category is human, machine or unknown, and t is seconds into the call. See Answers.
  • A new classification arrives every time the answer changes. Act on the one with the highest seq.
  • After {"type": "end"} you receive a summary with the final answer, and the socket closes.

Convert other recordings first, for example ffmpeg -i call.mp3 -ac 1 -ar 16000 -sample_fmt s16 call.wav. Live calls need no conversion: see Stream protocol.

The call is recorded for 30 days. To try it without recording, create the token with {"record": false} as the request body. See Data and recording.

Next steps