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

# Recorded calls

Upload a recorded call as a WAV file and get back the answers a live call would have received.

Upload a recorded call as a WAV file and get back the answers a live call would have received. Use it to test Noesis on your own calls before you connect live ones.

```
POST https://api.noesis.detectif.ai/v1/detect/file
```

The recording goes through the same analysis as a live call, in real time, so the labels and their timing are what a live call would have given. The request therefore takes about as long as the recording.

## Send a file

Send the file as a multipart field named `file`, with an `Idempotency-Key` so a retry does not analyse it twice:

```bash
curl https://api.noesis.detectif.ai/v1/detect/file \
  -H "Authorization: Bearer $DETECTIF_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "file=@call.wav" \
  -F "call_id=test-1"
```

```python
# analyse_call.py: send a recorded call to Noesis and print the answers
import os, sys, uuid

import requests

with open(sys.argv[1], "rb") as f:
    r = requests.post(
        "https://api.noesis.detectif.ai/v1/detect/file",
        headers={
            "Authorization": f"Bearer {os.environ['DETECTIF_API_KEY']}",
            "Idempotency-Key": str(uuid.uuid4()),
        },
        data={"call_id": "test-1"},
        files={"file": f},
        timeout=60,                       # between bytes: a newline arrives every 5 s
    )
result = r.json()
if r.status_code != 200 or result.get("type") == "error":
    sys.exit(result)                      # not a WAV file, balance too low, ...
for e in result["timeline"]:
    print(f'{e["t"]:6.1f}s  {e["label"]}  ({e["category"]})')
print("final:", result["final"]["label"] if result["final"] else "no speech heard")
```

```javascript
// analyse-call.mjs: send a recorded call to Noesis and print the answers (Node.js 18+)
import fs from "node:fs";
import { randomUUID } from "node:crypto";

const form = new FormData();
form.append("file", new Blob([fs.readFileSync(process.argv[2])]), "call.wav");
form.append("call_id", "test-1");
const r = await fetch("https://api.noesis.detectif.ai/v1/detect/file", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.DETECTIF_API_KEY}`, "Idempotency-Key": randomUUID() },
  body: form,
});
const result = await r.json();
if (!r.ok || result.type === "error") { console.error(result); process.exit(1); }
for (const e of result.timeline) console.log(`${e.t.toFixed(1).padStart(6)}s  ${e.label}  (${e.category})`);
console.log("final:", result.final ? result.final.label : "no speech heard");
```

The Python and Node.js samples print:

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

## Fields

| Field | Values | Default |
|---|---|---|
| `file` | The recording (required) | |
| `call_id` | Your reference, up to 200 characters, returned in the result | none |
| `remote_channel` | For a stereo file, the channel of the person you called: `0` or `1` | `0` |
| `language` | Optional hint, `en` or `hi`. Leave it out for automatic (English, Hindi, Hinglish) | automatic |
| `transcripts` | `false` to leave the transcript out of the result | `true` |
| `record` | `false` to not keep the recording and its transcript. See [Data and recording](https://platform.detectif.ai/docs/noesis/data-and-recording/) | `true` |

## Accepted files

| File | Accepted |
|---|---|
| Format | WAV with 8, 16, 24 or 32-bit PCM, 32 or 64-bit float, or G.711 μ-law or A-law samples |
| Sample rate | Any. Rates other than 8, 16, 22.05, 24, 44.1 and 48 kHz are converted to 16 kHz |
| Channels | Mono (the person you called), or stereo with your agent on the other channel |
| Length | 1 second to 15 minutes, up to 50 MB |

Convert other recordings, such as MP3, M4A or Opus, first: `ffmpeg -i call.mp3 -ac 1 -ar 16000 -sample_fmt s16 call.wav`.

## Response

```json
{
  "type": "result",
  "session_id": "9f2c4e7a-1b3d-4c5e-8f60-2a7b9c1d3e4f",
  "call_id": "test-1",
  "file": {"name": "call.wav", "duration_s": 26.6, "format": "pcm_s16le", "sample_rate": 8000, "channels": 1},
  "first": {"label": "ivr", "category": "machine", "confidence": 0.9, "t": 3.2},
  "final": {"label": "human", "category": "human", "confidence": 0.97},
  "timeline": [
    {"seq": 1, "t": 3.2, "label": "ivr", "category": "machine", "confidence": 0.9, "previous_label": "unknown", "change": "initial"},
    {"seq": 2, "t": 13.0, "label": "hold", "category": "machine", "confidence": 0.8, "previous_label": "ivr", "change": "transition"},
    {"seq": 3, "t": 23.4, "label": "human", "category": "human", "confidence": 0.95, "previous_label": "hold", "change": "transition"}
  ],
  "transcript": [{"t": 3.0, "text": "For credit cards, press one."}]
}
```

| Field | Meaning |
|---|---|
| `first` | The first answer, and when it came: `t` is seconds into the recording |
| `final` | The answer at the end of the recording. `first` and `final` are `null` when no speech was heard |
| `timeline` | Every answer in order, with the fields of a live call's `classification` events |
| `transcript` | What the callee said, with times. Left out when `transcripts=false` |
| `file` | What was analysed |
| `session_id` | Look the call up again later; see [Sessions and usage](https://platform.detectif.ai/docs/noesis/sessions-and-usage/) |

The full schema is in the [API Reference](https://platform.detectif.ai/docs/noesis/api/).

## While it runs

A 30-second recording takes about 30 seconds. The response starts straight away and a newline is sent every 5 seconds until the result, so the connection stays open however long the recording is; JSON parsers skip the newlines. If your HTTP client limits the total time of a request, set it above the recording's length plus a minute.

If the analysis fails after it has started, the response (status `200`) ends with `{"type": "error", ...}` instead of the result.

## Credits

A recording uses credits for its length, like a live call of the same length. A file that is refused uses none, and nothing is billed if the analysis fails. Send the same `Idempotency-Key` when you retry: the retry returns the stored result instead of running again.

## Errors

| Status | Meaning |
|---|---|
| `400` | No file was sent, it is not a WAV file, it is shorter than 1 second or has more than 2 channels, or `remote_channel=1` was set for a mono file |
| `401` | The API key is missing, invalid or revoked |
| `402` | Your credit balance does not cover the recording |
| `403` | The key is not allowed to use Noesis, or your email address is not verified |
| `413` | The file is larger than 50 MB or longer than 15 minutes |
| `429` | Too many requests or recordings at once |
| `503` | Busy; try again after the `Retry-After` seconds |

See [Errors](https://platform.detectif.ai/docs/noesis/errors/) for the error body and codes.
