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

# Noesis quickstart

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](https://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:

```bash
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:

```bash
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]))
```

```javascript
// noesis-quickstart.mjs: stream a recording to Noesis as if it were a live call
import fs from "node:fs";
import WebSocket from "ws";

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

const wav = fs.readFileSync(process.argv[2]);       // 16-bit PCM WAV, mono
let rate, pcm;
for (let pos = 12; pos + 8 <= wav.length; ) {
  const id = wav.toString("ascii", pos, pos + 4), size = wav.readUInt32LE(pos + 4);
  if (id === "fmt ") rate = wav.readUInt32LE(pos + 12);
  if (id === "data") { pcm = wav.subarray(pos + 8, pos + 8 + size); break; }
  pos += 8 + size + (size % 2);
}

const res = await fetch(`${API}/v1/detect/stream-tokens`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.DETECTIF_API_KEY}`, "Content-Type": "application/json" },
  body: "{}",
});
if (!res.ok) { console.error(await res.text()); process.exit(1); }
const { token } = await res.json();

const ws = new WebSocket(`${STREAM}?token=${token}&encoding=pcm_s16le&sample_rate=${rate}&call_id=test-1`);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

ws.on("message", async (raw) => {
  const e = JSON.parse(raw);
  if (e.type === "classification") console.log(`${e.t.toFixed(1).padStart(6)}s  ${e.label}  (${e.category})`);
  if (e.type === "summary") console.log("final:", e.label);
  if (e.type === "error") console.error("error:", e);
  if (e.type === "ready") {
    const step = rate * 2 / 50;                     // 20 ms of audio per frame
    for (let i = 0; i < pcm.length && ws.readyState === WebSocket.OPEN; i += step) {
      ws.send(pcm.subarray(i, i + step));
      await sleep(20);                              // real time, like a live call
    }
    if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: "end" }));
  }
});
ws.on("close", (code) => { if (code !== 1000) console.log("closed:", code); });
```

## 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](https://platform.detectif.ai/docs/noesis/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](https://platform.detectif.ai/docs/noesis/#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](https://platform.detectif.ai/docs/noesis/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](https://platform.detectif.ai/docs/noesis/data-and-recording/).

## Next steps

- [Twilio](https://platform.detectif.ai/docs/noesis/twilio/) and the other integration guides.
- [Stream protocol](https://platform.detectif.ai/docs/noesis/stream-protocol/): every message and event.
- [Recorded calls](https://platform.detectif.ai/docs/noesis/recorded-calls/): upload a WAV file instead of streaming it.
