{
  "openapi": "3.1.0",
  "info": {
    "title": "DetectifAI Noesis API",
    "summary": "Know live who or what answered a phone call.",
    "description": "Noesis tells you, live, who or what answered a phone call: a person, voicemail, an IVR menu or another\nmachine. Send the called person's audio over a WebSocket (or upload a recording) and get answers as the\ncall goes on. It speaks the same Detect protocol (paths, parameters and messages) as before, so existing clients\nwork by changing the host and the key.\n\n**Authentication.** Use a DetectifAI platform API key (`dtf_live_...`) with the scope `noesis:detect`\n(calls, files, stream tokens) or `noesis:read` (sessions, usage), in the `Authorization: Bearer` header\n(or `X-API-Key`). Keys in URLs are refused (`api_key_in_url`). Clients that cannot set headers (Twilio\n`<Stream>`, Exotel, Telnyx, Vonage) use a **stream token**: create one with\n`POST /v1/detect/stream-tokens` and pass it as `?token=ntk_...` on the WebSocket URL or as the Twilio\n`<Parameter name=\"token\" value=\"ntk_...\"/>`. A token works once, for 5 minutes, and comes with the\n`session_id` its call will have, so you can poll the results even when nothing comes back to you on the socket.\n\n**Live calls: `WS /v1/detect/stream`.** Query parameters: `encoding` (`mulaw` default, `alaw`, `pcm_s16le`),\n`sample_rate` (8000 default, 16000, 22050, 24000, 44100, 48000), `channels` (1 or 2), `remote_channel`\n(0 or 1: the channel of the person called when stereo), `language` (a hint such as `en`), `call_id`\n(your id, up to 200 characters), `transcripts` and `evidence` (send transcript / acoustic events too),\n`record` (`false` to not record the call), `token` (a stream token). `callback_url` is not offered.\n\nSend audio as binary frames (20-100 ms each) or as Twilio / Plivo media JSON (`TwilioMediaMessage`;\n`inbound` is the person called). Control messages: `{\"type\":\"start\",\"call_id\":\"...\"}`,\n`{\"type\":\"agent.speech\",\"state\":\"started|ended\"}`, `{\"type\":\"ping\"}` (answered with `pong`) and\n`{\"type\":\"end\"}` (Twilio: `stop`). You receive `ready` (`StreamReadyEvent`) when the call is open,\n`classification` events (`StreamClassificationEvent`) with the first answer and every change, optional\n`transcript` / `acoustic` events, then after `end` the `summary` (`StreamSummaryEvent`) followed by\n`{\"type\":\"usage\",\"billed_seconds\":n}` (`StreamUsageEvent`), and the socket closes. Results stay readable\nat `GET /v1/detect/sessions/{session_id}`.\n\nErrors arrive as `{\"type\":\"error\",\"code\",\"error\",\"status\"}` (`StreamErrorEvent`) before the socket closes.\nClose codes: 4401 authentication failed, 4402 balance too low (also during a call, when the balance runs\nout), 4403 not allowed (scope, e-mail not verified, account suspended), 4429 too many calls at once or too\nmany requests, 1003 invalid parameter, 1013 busy, try again shortly, 1011 error on our side, 1000 normal end.\n\n**Balance.** Calls and file analyses use your account balance, which is shown in the dashboard. A call that\nis refused, or fails on our side before it starts, uses nothing. On the unlimited plan nothing is charged. A\nfree account can run 2 calls or file analyses at the same time.\n\n**Recording.** Calls are recorded (audio and transcript) and kept for 30 days, then deleted. Send\n`record=false` (query parameter, stream-token field, Twilio parameter or form field) to not record a call.\n\n**Errors (HTTP).** Every non-2xx response uses `{\"error\": {\"type\", \"code\", \"message\", \"param\"}, \"request_id\"}`\nand carries `X-Request-Id`.\n\n**Example.** `wss://api.noesis.detectif.ai/v1/detect/stream?encoding=pcm_s16le&sample_rate=16000` with\n`Authorization: Bearer dtf_live_...`; or for Twilio:\n`<Connect><Stream url=\"wss://api.noesis.detectif.ai/v1/detect/stream\"><Parameter name=\"token\" value=\"ntk_Q7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xa\"/></Stream></Connect>`.",
    "contact": {
      "name": "DetectifAI developer support",
      "url": "https://platform.detectif.ai/docs/noesis"
    },
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.noesis.detectif.ai",
      "description": "Production"
    }
  ],
  "paths": {
    "/v1/detect/stream-tokens": {
      "post": {
        "tags": [
          "Streams"
        ],
        "summary": "Create a stream token",
        "description": "Creates a single-use token for one live call, valid for 5 minutes, for clients that cannot send an Authorization header (Twilio <Stream>, Exotel, Telnyx, Vonage). Pass it as `?token=` on the WebSocket URL or as the Twilio `<Parameter name=\"token\">`. The response also has the `session_id` the call will use: poll `GET /v1/detect/sessions/{session_id}` for live state and the final answer. Send `{\"record\": false}` to not record that call. Does not use your balance. Requires scope `noesis:detect`.",
        "operationId": "createStreamToken",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/StreamTokenRequest"
                  },
                  {
                    "type": "null"
                  }
                ],
                "title": "Payload"
              },
              "examples": {
                "default": {
                  "summary": "Recorded call (default)",
                  "value": {}
                },
                "no_recording": {
                  "summary": "Do not record this call",
                  "value": {
                    "record": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StreamToken"
                },
                "example": {
                  "request_id": "req_8f14e45fceea167a5a36dedd",
                  "object": "noesis.stream_token",
                  "token": "ntk_Q7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xa",
                  "session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
                  "expires_at": "2026-10-19T09:35:00Z",
                  "expires_in": 300,
                  "record": true
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid, for example an API key in the URL or an unsupported parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "code": "invalid_body",
                    "message": "The request body must be a JSON object."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "authentication",
                    "code": "invalid_api_key",
                    "message": "The API key is invalid."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, the e-mail address is not verified, or the account is suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "permission",
                    "code": "insufficient_scope",
                    "message": "This API key is missing the required scope(s): noesis:detect."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests, or too many calls and file analyses at the same time. Wait for Retry-After seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "code": "concurrency_limited",
                    "message": "At most 2 Noesis calls or file analyses may run at the same time per account."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "internal",
                    "code": "internal_error",
                    "message": "An internal error occurred."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/detect/sessions/{session_id}": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Get a call session",
        "description": "Returns one of your calls or file analyses by session id (from the stream token or the socket's ready message). During a call: `status` `active`, the latest classification (`latest`), the audio seconds received so far and the timestamps. After it: the final answer (`label`, `category`, `confidence`, `decided`, `timeline`). A token's session not started yet is `waiting`. Sessions of other accounts return 404. Details are kept for 30 days; after that the session keeps its times, seconds and final answer only. Requires scope `noesis:read`.",
        "operationId": "getSession",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "The session id from the stream token or the socket's ready message.",
              "examples": [
                "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f"
              ],
              "title": "Session Id"
            },
            "description": "The session id from the stream token or the socket's ready message."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoesisSession"
                },
                "example": {
                  "request_id": "req_8f14e45fceea167a5a36dedd",
                  "type": "session",
                  "object": "noesis.session",
                  "session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
                  "stream_session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
                  "operation_id": "op_6f1ed002ab5595859014ebf0951522d9",
                  "status": "completed",
                  "closed": true,
                  "call_id": "CA0123456789abcdef0123456789abcdef",
                  "transport": "websocket",
                  "orchestration": "twilio",
                  "encoding": "mulaw",
                  "sample_rate": 8000,
                  "channels": 1,
                  "record": true,
                  "started_at": "2026-10-19T09:30:00Z",
                  "ready_at": "2026-10-19T09:30:00Z",
                  "ended_at": "2026-10-19T09:31:12Z",
                  "end_reason": "client_end",
                  "audio_seconds": 71.4,
                  "billed_seconds": 71.4,
                  "latest": {
                    "type": "classification",
                    "seq": 1,
                    "version": 1,
                    "t": 4.2,
                    "label": "human",
                    "category": "human",
                    "confidence": 0.93,
                    "change": "initial",
                    "trigger": "speech",
                    "reason": "A person answered and greeted the caller."
                  },
                  "events": [
                    {
                      "type": "classification",
                      "seq": 1,
                      "version": 1,
                      "t": 4.2,
                      "label": "human",
                      "category": "human",
                      "confidence": 0.93,
                      "change": "initial",
                      "trigger": "speech",
                      "reason": "A person answered and greeted the caller."
                    }
                  ],
                  "label": "human",
                  "category": "human",
                  "confidence": 0.93,
                  "decided": true,
                  "timeline": [
                    {
                      "type": "classification",
                      "seq": 1,
                      "version": 1,
                      "t": 4.2,
                      "label": "human",
                      "category": "human",
                      "confidence": 0.93,
                      "change": "initial",
                      "trigger": "speech",
                      "reason": "A person answered and greeted the caller."
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid, for example an API key in the URL or an unsupported parameter.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "code": "api_key_in_url",
                    "message": "API keys are not accepted in URLs or stream parameters.",
                    "param": "api_key"
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "authentication",
                    "code": "invalid_api_key",
                    "message": "The API key is invalid."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, the e-mail address is not verified, or the account is suspended.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "permission",
                    "code": "insufficient_scope",
                    "message": "This API key is missing the required scope(s): noesis:detect."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No session with this id exists in your account.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "not_found",
                    "code": "session_not_found",
                    "message": "No session with this id exists in your account.",
                    "param": "session_id"
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests, or too many calls and file analyses at the same time. Wait for Retry-After seconds.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "code": "concurrency_limited",
                    "message": "At most 2 Noesis calls or file analyses may run at the same time per account."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "internal",
                    "code": "internal_error",
                    "message": "An internal error occurred."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/detect/sessions": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "List call sessions",
        "description": "Lists your calls and file analyses, newest first. Filter with `call_id` to find the session of a provider call (Twilio `callSid`, Plivo `callUuid`, taken from the start message) or of your own call id. Page with `limit` and `cursor`. Requires scope `noesis:read`.",
        "operationId": "listSessions",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "call_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "description": "Only sessions of this call id (yours, or Twilio's callSid / Plivo's callUuid).",
              "examples": [
                "CA0123456789abcdef0123456789abcdef"
              ],
              "title": "Call Id"
            },
            "description": "Only sessions of this call id (yours, or Twilio's callSid / Plivo's callUuid)."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 100,
              "minimum": 1,
              "description": "How many sessions to return (1-100).",
              "examples": [
                20
              ],
              "default": 20,
              "title": "Limit"
            },
            "description": "How many sessions to return (1-100)."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 256
                },
                {
                  "type": "null"
                }
              ],
              "description": "`next_cursor` from the previous page.",
              "examples": [
                "eyJ0IjoiMjAyNi0xMC0xOSJ9"
              ],
              "title": "Cursor"
            },
            "description": "`next_cursor` from the previous page."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoesisSessionList"
                },
                "example": {
                  "request_id": "req_8f14e45fceea167a5a36dedd",
                  "object": "list",
                  "data": [
                    {
                      "type": "session",
                      "object": "noesis.session",
                      "session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
                      "stream_session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
                      "operation_id": "op_6f1ed002ab5595859014ebf0951522d9",
                      "status": "completed",
                      "closed": true,
                      "call_id": "CA0123456789abcdef0123456789abcdef",
                      "transport": "websocket",
                      "orchestration": "twilio",
                      "encoding": "mulaw",
                      "sample_rate": 8000,
                      "channels": 1,
                      "record": true,
                      "started_at": "2026-10-19T09:30:00Z",
                      "ready_at": "2026-10-19T09:30:00Z",
                      "ended_at": "2026-10-19T09:31:12Z",
                      "end_reason": "client_end",
                      "audio_seconds": 71.4,
                      "billed_seconds": 71.4,
                      "latest": {
                        "type": "classification",
                        "seq": 1,
                        "version": 1,
                        "t": 4.2,
                        "label": "human",
                        "category": "human",
                        "confidence": 0.93,
                        "change": "initial",
                        "trigger": "speech",
                        "reason": "A person answered and greeted the caller."
                      },
                      "events": [
                        {
                          "type": "classification",
                          "seq": 1,
                          "version": 1,
                          "t": 4.2,
                          "label": "human",
                          "category": "human",
                          "confidence": 0.93,
                          "change": "initial",
                          "trigger": "speech",
                          "reason": "A person answered and greeted the caller."
                        }
                      ],
                      "label": "human",
                      "category": "human",
                      "confidence": 0.93
                    }
                  ],
                  "has_more": false
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid, for example an API key in the URL or an unsupported parameter.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "code": "api_key_in_url",
                    "message": "API keys are not accepted in URLs or stream parameters.",
                    "param": "api_key"
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "authentication",
                    "code": "invalid_api_key",
                    "message": "The API key is invalid."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, the e-mail address is not verified, or the account is suspended.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "permission",
                    "code": "insufficient_scope",
                    "message": "This API key is missing the required scope(s): noesis:detect."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests, or too many calls and file analyses at the same time. Wait for Retry-After seconds.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "code": "concurrency_limited",
                    "message": "At most 2 Noesis calls or file analyses may run at the same time per account."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "internal",
                    "code": "internal_error",
                    "message": "An internal error occurred."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/detect/usage": {
      "get": {
        "tags": [
          "Usage"
        ],
        "summary": "Get your Noesis usage",
        "description": "Your Noesis calls and call minutes over the last `days` days (default 30): how many calls and file analyses, seconds of audio, billed minutes and what was charged, and how many run right now. Requires scope `noesis:read`.",
        "operationId": "getUsage",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 90,
              "minimum": 1,
              "description": "Length of the period in days, ending now (1-90).",
              "examples": [
                30
              ],
              "default": 30,
              "title": "Days"
            },
            "description": "Length of the period in days, ending now (1-90)."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoesisUsage"
                },
                "example": {
                  "request_id": "req_8f14e45fceea167a5a36dedd",
                  "object": "noesis.usage",
                  "days": 30,
                  "period": {
                    "start": "2026-09-19T09:30:00Z",
                    "end": "2026-10-19T09:30:00Z"
                  },
                  "calls": 12,
                  "calls_used": 12,
                  "streams": 10,
                  "files": 2,
                  "audio_seconds": 1284.5,
                  "billed_seconds": 1280.0,
                  "minutes": 21.33,
                  "active_sessions": 1,
                  "concurrent_limit": 2,
                  "plan": "free",
                  "unlimited": false
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid, for example an API key in the URL or an unsupported parameter.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "code": "api_key_in_url",
                    "message": "API keys are not accepted in URLs or stream parameters.",
                    "param": "api_key"
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "authentication",
                    "code": "invalid_api_key",
                    "message": "The API key is invalid."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, the e-mail address is not verified, or the account is suspended.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "permission",
                    "code": "insufficient_scope",
                    "message": "This API key is missing the required scope(s): noesis:detect."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests, or too many calls and file analyses at the same time. Wait for Retry-After seconds.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "code": "concurrency_limited",
                    "message": "At most 2 Noesis calls or file analyses may run at the same time per account."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "internal",
                    "code": "internal_error",
                    "message": "An internal error occurred."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/detect/file": {
      "post": {
        "tags": [
          "Recordings"
        ],
        "summary": "Analyse a call recording",
        "description": "Analyses a recorded call as if it were live and returns the answer. Send a WAV file (PCM, float or G.711; 1 second to 15 minutes, at most 50 MB) as multipart/form-data in the `file` field, or as the request body. Options as query parameters or form fields: `call_id`, `language`, `remote_channel` (stereo files), `transcripts` (default true), `record` (`false` to not keep the recording). The file is replayed in real time, so the answer takes about as long as the recording: the response (200) starts at once, sends a newline every few seconds to keep the connection open, and ends with one JSON object, the result (`type: result`) or `{\"type\":\"error\",...}` if the analysis failed (then nothing is charged). File analyses use your account balance. Send an `Idempotency-Key` to make retries safe (24 h). Requires scope `noesis:detect`.",
        "operationId": "analyzeRecording",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128
                },
                {
                  "type": "null"
                }
              ],
              "description": "Optional (recommended) key that makes retries safe for 24 h: a retry with the same key and the same request returns the stored response without charging again.",
              "examples": [
                "9b1f6c1e-2c1a-4d6e-9a43-2f4f5b6f7a10"
              ],
              "title": "Idempotency-Key"
            },
            "description": "Optional (recommended) key that makes retries safe for 24 h: a retry with the same key and the same request returns the stored response without charging again."
          }
        ],
        "responses": {
          "200": {
            "description": "The analysis runs: newlines while it works, then one JSON object (this result, or {\"type\":\"error\",\"code\",\"error\",\"status\"}).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileResult"
                },
                "example": {
                  "type": "result",
                  "session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
                  "call_id": "crm-4711",
                  "file": {
                    "name": "call.wav",
                    "duration_s": 42.5,
                    "format": "pcm_s16le",
                    "sample_rate": 8000,
                    "channels": 1
                  },
                  "first": {
                    "label": "voicemail",
                    "category": "machine",
                    "confidence": 0.88,
                    "t": 3.1
                  },
                  "final": {
                    "label": "voicemail",
                    "category": "machine",
                    "confidence": 0.95
                  },
                  "timeline": [
                    {
                      "seq": 1,
                      "t": 3.1,
                      "label": "voicemail",
                      "category": "machine",
                      "confidence": 0.88,
                      "change": "initial",
                      "reason": "A greeting asks to leave a message."
                    }
                  ],
                  "transcript": [
                    {
                      "t": 0.8,
                      "text": "Hi, you've reached Ada. Leave a message."
                    }
                  ],
                  "usage": {
                    "audio_s": 42.5,
                    "transcribed_s": 42.5,
                    "checks": 3
                  },
                  "operation_id": "op_6f1ed002ab5595859014ebf0951522d9",
                  "billed_seconds": 42.5,
                  "idempotent_replay": false
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid, for example an API key in the URL or an unsupported parameter.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "code": "api_key_in_url",
                    "message": "API keys are not accepted in URLs or stream parameters.",
                    "param": "api_key"
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, invalid or revoked.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "authentication",
                    "code": "invalid_api_key",
                    "message": "The API key is invalid."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Your balance is too low for this call or file.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "insufficient_credits",
                    "code": "insufficient_credits",
                    "message": "Your balance is too low for this call."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, the e-mail address is not verified, or the account is suspended.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "permission",
                    "code": "insufficient_scope",
                    "message": "This API key is missing the required scope(s): noesis:detect."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "The upload did not arrive in time.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "code": "upload_timeout",
                    "message": "The upload did not arrive in time; retry on a faster connection.",
                    "param": "file"
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A request with the same Idempotency-Key is still running.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "conflict",
                    "code": "operation_in_progress",
                    "message": "A request with this Idempotency-Key is still being processed."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd",
                  "operation_id": "op_6f1ed002ab5595859014ebf0951522d9"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The file is larger than 50 MB.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "payload_too_large",
                    "code": "file_too_large",
                    "message": "The file is larger than 50 MB.",
                    "param": "file"
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Not a WAV file.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "unsupported_media",
                    "code": "unsupported_format",
                    "message": "This is not a WAV file (no RIFF/WAVE header).",
                    "param": "file"
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The recording is too short or too long, or the Idempotency-Key was used for a different request.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "unprocessable_audio",
                    "code": "audio_too_long",
                    "message": "The recording must be at most 15 minutes long.",
                    "param": "file"
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests, or too many calls and file analyses at the same time. Wait for Retry-After seconds.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "code": "concurrency_limited",
                    "message": "At most 2 Noesis calls or file analyses may run at the same time per account."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "internal",
                    "code": "internal_error",
                    "message": "An internal error occurred."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Noesis is busy or temporarily unavailable. Retry after the Retry-After seconds.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "type": "overloaded",
                    "code": "noesis_capacity",
                    "message": "Noesis is at capacity right now; retry shortly."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "description": "multipart/form-data with the WAV in the `file` field, or the WAV itself as the body.",
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "contentMediaType": "application/octet-stream",
                    "description": "The WAV file (PCM, float or G.711; 1 s to 15 min; at most 50 MB)."
                  },
                  "call_id": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Your id for the call.",
                    "examples": [
                      "crm-4711"
                    ]
                  },
                  "language": {
                    "type": "string",
                    "maxLength": 16,
                    "description": "Language hint, e.g. en or hi.",
                    "examples": [
                      "en"
                    ]
                  },
                  "remote_channel": {
                    "type": "integer",
                    "enum": [
                      0,
                      1
                    ],
                    "default": 0,
                    "description": "Stereo files: the channel of the person called.",
                    "examples": [
                      0
                    ]
                  },
                  "transcripts": {
                    "type": "boolean",
                    "default": true,
                    "description": "Include the transcript.",
                    "examples": [
                      true
                    ]
                  },
                  "record": {
                    "type": "boolean",
                    "default": true,
                    "description": "Keep the recording and transcript for 30 days; false to not keep them.",
                    "examples": [
                      false
                    ]
                  }
                }
              },
              "encoding": {
                "file": {
                  "contentType": "audio/wav, application/octet-stream"
                }
              },
              "example": {
                "file": "call.wav",
                "call_id": "crm-4711"
              }
            },
            "audio/wav": {
              "schema": {
                "type": "string",
                "format": "binary",
                "contentMediaType": "audio/wav"
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Check service health",
        "description": "Whether the service and its parts are up: `status` is `ok`, or `degraded` when a part is unavailable. No authentication needed.",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoesisHealth"
                },
                "example": {
                  "request_id": "req_8f14e45fceea167a5a36dedd",
                  "status": "ok",
                  "version": "1.0.0",
                  "database": "ok",
                  "detection": "ok"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests, or too many calls and file analyses at the same time. Wait for Retry-After seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "code": "concurrency_limited",
                    "message": "At most 2 Noesis calls or file analyses may run at the same time per account."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "internal",
                    "code": "internal_error",
                    "message": "An internal error occurred."
                  },
                  "request_id": "req_8f14e45fceea167a5a36dedd"
                }
              }
            }
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "schemas": {
      "CreditsCharged": {
        "properties": {
          "charged": {
            "type": "integer",
            "title": "Charged",
            "description": "Credits used by this request. A repeated request with the same Idempotency-Key uses none: it reports 0 here and what the original request used in `original_charge`."
          },
          "balance": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Balance",
            "description": "Your available credits after this request; null on the unlimited plan."
          },
          "original_charge": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Original Charge",
            "description": "Only on repeated requests (`charged` is then 0): the credits the original request used."
          },
          "unlimited": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Unlimited",
            "description": "Present (true) only for accounts on the unlimited plan, where checks use no credits."
          }
        },
        "type": "object",
        "required": [
          "charged",
          "balance"
        ],
        "title": "CreditsCharged"
      },
      "EmbeddedDeepfake": {
        "properties": {
          "verdict": {
            "type": "string",
            "enum": [
              "real",
              "fake",
              "not_sure"
            ],
            "title": "Verdict",
            "description": "The decision: `real`, `fake` (AI-generated), or `not_sure` (try a longer or cleaner recording)."
          },
          "label": {
            "type": "string",
            "enum": [
              "synthetic",
              "genuine",
              "uncertain"
            ],
            "title": "Label",
            "description": "The same decision in the older wording: `synthetic` (fake), `genuine` (real), `uncertain` (not sure)."
          },
          "is_synthetic": {
            "type": "boolean",
            "title": "Is Synthetic",
            "description": "true only when the verdict is `fake`."
          },
          "score": {
            "type": "number",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Score",
            "description": "How likely the recording is AI-generated, from 0 to 1."
          },
          "threshold": {
            "type": "number",
            "title": "Threshold",
            "description": "The main model's threshold (0.62). Informational: decide with `verdict`."
          },
          "score_semantics": {
            "type": "string",
            "title": "Score Semantics",
            "description": "A short reminder of what `score` means."
          },
          "duration_seconds": {
            "type": "number",
            "title": "Duration Seconds",
            "description": "Length of the checked audio, in seconds."
          },
          "segments": {
            "items": {
              "$ref": "#/components/schemas/Segment"
            },
            "type": "array",
            "title": "Segments",
            "description": "Scores for consecutive, overlapping parts of the recording."
          },
          "operation_id": {
            "type": "string",
            "title": "Operation Id"
          },
          "credits_charged": {
            "type": "integer",
            "title": "Credits Charged"
          },
          "model": {
            "$ref": "#/components/schemas/ModelInfo"
          }
        },
        "type": "object",
        "required": [
          "verdict",
          "label",
          "is_synthetic",
          "score",
          "threshold",
          "score_semantics",
          "duration_seconds",
          "segments",
          "operation_id",
          "credits_charged",
          "model"
        ],
        "title": "EmbeddedDeepfake"
      },
      "Error": {
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorDetail"
          },
          "request_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Request Id",
            "description": "Also in the X-Request-Id header; quote it when contacting support."
          },
          "operation_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Operation Id",
            "description": "Errors about a specific operation (e.g. 409 operation_in_progress): its id."
          },
          "credits": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RequiredCredits"
              },
              {
                "type": "null"
              }
            ],
            "description": "402 insufficient_credits only."
          },
          "partial": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PartialOutcome"
              },
              {
                "type": "null"
              }
            ],
            "description": "Verification with detect_deepfake=true only: the synthetic-speech result, when that check completed although the verification failed."
          },
          "idempotent_replay": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Idempotent Replay",
            "description": "true when this is a stored error replayed for a retry."
          }
        },
        "additionalProperties": true,
        "type": "object",
        "required": [
          "error",
          "request_id"
        ],
        "title": "Error",
        "description": "The error envelope of every non-2xx response.",
        "examples": [
          {
            "error": {
              "code": "missing_file",
              "message": "A \"file\" field with the audio is required.",
              "param": "file",
              "type": "invalid_request"
            },
            "request_id": "req_8f14e45fceea167a5a36dedd"
          }
        ]
      },
      "ErrorDetail": {
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "invalid_request",
              "authentication",
              "permission",
              "not_found",
              "conflict",
              "rate_limited",
              "insufficient_credits",
              "payload_too_large",
              "unsupported_media",
              "unprocessable_audio",
              "overloaded",
              "internal"
            ],
            "title": "Type"
          },
          "code": {
            "type": "string",
            "title": "Code",
            "description": "Stable snake_case error code."
          },
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human-readable explanation. Do not parse."
          },
          "param": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Param",
            "description": "The request field the error refers to, if any."
          }
        },
        "type": "object",
        "required": [
          "type",
          "code",
          "message"
        ],
        "title": "ErrorDetail"
      },
      "FileResult": {
        "properties": {
          "type": {
            "type": "string",
            "const": "result",
            "title": "Type"
          },
          "session_id": {
            "type": "string",
            "title": "Session Id"
          },
          "call_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Call Id"
          },
          "file": {
            "additionalProperties": true,
            "type": "object",
            "title": "File"
          },
          "first": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "First"
          },
          "final": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Final"
          },
          "timeline": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Timeline"
          },
          "transcript": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Transcript"
          },
          "usage": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Usage"
          },
          "operation_id": {
            "type": "string",
            "title": "Operation Id"
          },
          "billed_seconds": {
            "type": "number",
            "title": "Billed Seconds"
          },
          "credits": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Credits",
            "description": "What the analysis used from your balance."
          },
          "idempotent_replay": {
            "type": "boolean",
            "title": "Idempotent Replay"
          }
        },
        "additionalProperties": true,
        "type": "object",
        "required": [
          "type",
          "session_id",
          "file",
          "first",
          "final",
          "timeline",
          "operation_id",
          "billed_seconds",
          "idempotent_replay"
        ],
        "title": "FileResult",
        "examples": [
          {
            "billed_seconds": 42.5,
            "call_id": "crm-4711",
            "file": {
              "channels": 1,
              "duration_s": 42.5,
              "format": "pcm_s16le",
              "name": "call.wav",
              "sample_rate": 8000
            },
            "final": {
              "category": "machine",
              "confidence": 0.95,
              "label": "voicemail"
            },
            "first": {
              "category": "machine",
              "confidence": 0.88,
              "label": "voicemail",
              "t": 3.1
            },
            "idempotent_replay": false,
            "operation_id": "op_6f1ed002ab5595859014ebf0951522d9",
            "session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
            "timeline": [
              {
                "category": "machine",
                "change": "initial",
                "confidence": 0.88,
                "label": "voicemail",
                "reason": "A greeting asks to leave a message.",
                "seq": 1,
                "t": 3.1
              }
            ],
            "transcript": [
              {
                "t": 0.8,
                "text": "Hi, you've reached Ada. Leave a message."
              }
            ],
            "type": "result",
            "usage": {
              "audio_s": 42.5,
              "checks": 3,
              "transcribed_s": 42.5
            }
          }
        ]
      },
      "ModelInfo": {
        "properties": {
          "name": {
            "type": "string",
            "title": "Name",
            "description": "Model family: `aletheia` (deepfake detection) or `eidos` (speaker verification)."
          },
          "version": {
            "type": "string",
            "title": "Version",
            "description": "Opaque model version; it changes whenever the model is updated."
          }
        },
        "type": "object",
        "required": [
          "name",
          "version"
        ],
        "title": "ModelInfo"
      },
      "NoesisHealth": {
        "properties": {
          "request_id": {
            "type": "string",
            "title": "Request Id"
          },
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "degraded"
            ],
            "title": "Status"
          },
          "version": {
            "type": "string",
            "title": "Version"
          },
          "database": {
            "type": "string",
            "enum": [
              "ok",
              "unavailable"
            ],
            "title": "Database"
          },
          "detection": {
            "type": "string",
            "enum": [
              "ok",
              "unavailable"
            ],
            "title": "Detection"
          }
        },
        "type": "object",
        "required": [
          "request_id",
          "status",
          "version",
          "database",
          "detection"
        ],
        "title": "NoesisHealth",
        "examples": [
          {
            "database": "ok",
            "detection": "ok",
            "request_id": "req_8f14e45fceea167a5a36dedd",
            "status": "ok",
            "version": "1.0.0"
          }
        ]
      },
      "NoesisSession": {
        "properties": {
          "request_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Request Id"
          },
          "type": {
            "type": "string",
            "const": "session",
            "title": "Type",
            "default": "session"
          },
          "object": {
            "type": "string",
            "const": "noesis.session",
            "title": "Object",
            "default": "noesis.session"
          },
          "session_id": {
            "type": "string",
            "title": "Session Id"
          },
          "stream_session_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Stream Session Id",
            "description": "The session id in the socket's ready message (same as session_id or an alias of it; both work for lookups)."
          },
          "operation_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Operation Id"
          },
          "status": {
            "type": "string",
            "enum": [
              "waiting",
              "expired",
              "active",
              "completed",
              "interrupted",
              "failed"
            ],
            "title": "Status",
            "description": "waiting: the stream token was created but not used yet; active: the call is running; completed / interrupted / failed: the call ended."
          },
          "closed": {
            "type": "boolean",
            "title": "Closed"
          },
          "call_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Call Id",
            "description": "Your call id, or the provider's (Twilio callSid, Plivo callUuid)."
          },
          "transport": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "websocket",
                  "file"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Transport"
          },
          "orchestration": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Orchestration",
            "description": "binary, json, twilio, plivo or file."
          },
          "encoding": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Encoding"
          },
          "sample_rate": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Sample Rate"
          },
          "channels": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Channels"
          },
          "record": {
            "type": "boolean",
            "title": "Record"
          },
          "started_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Started At"
          },
          "ready_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ready At"
          },
          "ended_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ended At"
          },
          "end_reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "End Reason"
          },
          "audio_seconds": {
            "type": "number",
            "title": "Audio Seconds",
            "description": "Seconds of the called person's audio received so far."
          },
          "billed_seconds": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Billed Seconds"
          },
          "credits_charged": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Credits Charged"
          },
          "latest": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Latest",
            "description": "The latest classification event (live during the call)."
          },
          "events": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Events",
            "description": "Classification events, oldest first."
          },
          "error": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Error"
          },
          "label": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Label",
            "description": "The answer: the final one after the call, the latest during it."
          },
          "category": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Category"
          },
          "confidence": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Confidence"
          },
          "decided": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Decided"
          },
          "timeline": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Timeline"
          }
        },
        "additionalProperties": true,
        "type": "object",
        "required": [
          "session_id",
          "status",
          "closed",
          "record",
          "audio_seconds"
        ],
        "title": "NoesisSession",
        "examples": [
          {
            "audio_seconds": 71.4,
            "billed_seconds": 71.4,
            "call_id": "CA0123456789abcdef0123456789abcdef",
            "category": "human",
            "channels": 1,
            "closed": true,
            "confidence": 0.93,
            "decided": true,
            "encoding": "mulaw",
            "end_reason": "client_end",
            "ended_at": "2026-10-19T09:31:12Z",
            "events": [
              {
                "category": "human",
                "change": "initial",
                "confidence": 0.93,
                "label": "human",
                "reason": "A person answered and greeted the caller.",
                "seq": 1,
                "t": 4.2,
                "trigger": "speech",
                "type": "classification",
                "version": 1
              }
            ],
            "label": "human",
            "latest": {
              "category": "human",
              "change": "initial",
              "confidence": 0.93,
              "label": "human",
              "reason": "A person answered and greeted the caller.",
              "seq": 1,
              "t": 4.2,
              "trigger": "speech",
              "type": "classification",
              "version": 1
            },
            "object": "noesis.session",
            "operation_id": "op_6f1ed002ab5595859014ebf0951522d9",
            "orchestration": "twilio",
            "ready_at": "2026-10-19T09:30:00Z",
            "record": true,
            "request_id": "req_8f14e45fceea167a5a36dedd",
            "sample_rate": 8000,
            "session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
            "started_at": "2026-10-19T09:30:00Z",
            "status": "completed",
            "stream_session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
            "timeline": [
              {
                "category": "human",
                "change": "initial",
                "confidence": 0.93,
                "label": "human",
                "reason": "A person answered and greeted the caller.",
                "seq": 1,
                "t": 4.2,
                "trigger": "speech",
                "type": "classification",
                "version": 1
              }
            ],
            "transport": "websocket",
            "type": "session"
          }
        ]
      },
      "NoesisSessionList": {
        "properties": {
          "request_id": {
            "type": "string",
            "title": "Request Id"
          },
          "object": {
            "type": "string",
            "const": "list",
            "title": "Object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/NoesisSession"
            },
            "type": "array",
            "title": "Data"
          },
          "has_more": {
            "type": "boolean",
            "title": "Has More"
          },
          "next_cursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Next Cursor"
          }
        },
        "type": "object",
        "required": [
          "request_id",
          "object",
          "data",
          "has_more",
          "next_cursor"
        ],
        "title": "NoesisSessionList",
        "examples": [
          {
            "data": [
              {
                "audio_seconds": 71.4,
                "billed_seconds": 71.4,
                "call_id": "CA0123456789abcdef0123456789abcdef",
                "category": "human",
                "channels": 1,
                "closed": true,
                "confidence": 0.93,
                "encoding": "mulaw",
                "end_reason": "client_end",
                "ended_at": "2026-10-19T09:31:12Z",
                "events": [
                  {
                    "category": "human",
                    "change": "initial",
                    "confidence": 0.93,
                    "label": "human",
                    "reason": "A person answered and greeted the caller.",
                    "seq": 1,
                    "t": 4.2,
                    "trigger": "speech",
                    "type": "classification",
                    "version": 1
                  }
                ],
                "label": "human",
                "latest": {
                  "category": "human",
                  "change": "initial",
                  "confidence": 0.93,
                  "label": "human",
                  "reason": "A person answered and greeted the caller.",
                  "seq": 1,
                  "t": 4.2,
                  "trigger": "speech",
                  "type": "classification",
                  "version": 1
                },
                "object": "noesis.session",
                "operation_id": "op_6f1ed002ab5595859014ebf0951522d9",
                "orchestration": "twilio",
                "ready_at": "2026-10-19T09:30:00Z",
                "record": true,
                "sample_rate": 8000,
                "session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
                "started_at": "2026-10-19T09:30:00Z",
                "status": "completed",
                "stream_session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
                "transport": "websocket",
                "type": "session"
              }
            ],
            "has_more": false,
            "object": "list",
            "request_id": "req_8f14e45fceea167a5a36dedd"
          }
        ]
      },
      "NoesisUsage": {
        "properties": {
          "request_id": {
            "type": "string",
            "title": "Request Id"
          },
          "object": {
            "type": "string",
            "const": "noesis.usage",
            "title": "Object"
          },
          "days": {
            "type": "integer",
            "title": "Days"
          },
          "period": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object",
            "title": "Period"
          },
          "calls": {
            "type": "integer",
            "title": "Calls",
            "description": "Calls and file analyses started in the period."
          },
          "calls_used": {
            "type": "integer",
            "title": "Calls Used",
            "description": "Same as calls (kept for older clients)."
          },
          "streams": {
            "type": "integer",
            "title": "Streams"
          },
          "files": {
            "type": "integer",
            "title": "Files"
          },
          "audio_seconds": {
            "type": "number",
            "title": "Audio Seconds"
          },
          "billed_seconds": {
            "type": "number",
            "title": "Billed Seconds"
          },
          "minutes": {
            "type": "number",
            "title": "Minutes",
            "description": "Billed call minutes (billed_seconds / 60)."
          },
          "credits_charged": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Credits Charged",
            "description": "What the calls used from your balance."
          },
          "active_sessions": {
            "type": "integer",
            "title": "Active Sessions"
          },
          "concurrent_limit": {
            "type": "integer",
            "title": "Concurrent Limit",
            "description": "How many calls or file analyses may run at the same time."
          },
          "plan": {
            "type": "string",
            "enum": [
              "free",
              "unlimited"
            ],
            "title": "Plan"
          },
          "unlimited": {
            "type": "boolean",
            "title": "Unlimited"
          }
        },
        "type": "object",
        "required": [
          "request_id",
          "object",
          "days",
          "period",
          "calls",
          "calls_used",
          "streams",
          "files",
          "audio_seconds",
          "billed_seconds",
          "minutes",
          "active_sessions",
          "concurrent_limit",
          "plan",
          "unlimited"
        ],
        "title": "NoesisUsage",
        "examples": [
          {
            "active_sessions": 1,
            "audio_seconds": 1284.5,
            "billed_seconds": 1280.0,
            "calls": 12,
            "calls_used": 12,
            "concurrent_limit": 2,
            "days": 30,
            "files": 2,
            "minutes": 21.33,
            "object": "noesis.usage",
            "period": {
              "end": "2026-10-19T09:30:00Z",
              "start": "2026-09-19T09:30:00Z"
            },
            "plan": "free",
            "request_id": "req_8f14e45fceea167a5a36dedd",
            "streams": 10,
            "unlimited": false
          }
        ]
      },
      "PartialOutcome": {
        "properties": {
          "deepfake": {
            "$ref": "#/components/schemas/EmbeddedDeepfake",
            "description": "The synthetic-speech check, which completed."
          },
          "credits": {
            "$ref": "#/components/schemas/CreditsCharged"
          }
        },
        "type": "object",
        "required": [
          "deepfake",
          "credits"
        ],
        "title": "PartialOutcome"
      },
      "RequiredCredits": {
        "properties": {
          "available": {
            "type": "integer",
            "title": "Available",
            "description": "Credits you have available."
          },
          "required": {
            "type": "integer",
            "title": "Required",
            "description": "Credits this request needs."
          }
        },
        "type": "object",
        "required": [
          "available",
          "required"
        ],
        "title": "RequiredCredits"
      },
      "Segment": {
        "properties": {
          "start": {
            "type": "number",
            "title": "Start",
            "description": "Start of this part of the recording, in seconds."
          },
          "end": {
            "type": "number",
            "title": "End",
            "description": "End of this part of the recording, in seconds."
          },
          "score": {
            "type": "number",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Score",
            "description": "How likely this part is synthetic (0 to 1)."
          }
        },
        "type": "object",
        "required": [
          "start",
          "end",
          "score"
        ],
        "title": "Segment"
      },
      "StreamToken": {
        "properties": {
          "request_id": {
            "type": "string",
            "title": "Request Id"
          },
          "object": {
            "type": "string",
            "const": "noesis.stream_token",
            "title": "Object"
          },
          "token": {
            "type": "string",
            "title": "Token",
            "description": "Single-use token (ntk_...), valid for 5 minutes. Pass it as ?token= or as the Twilio <Parameter name=\"token\">."
          },
          "session_id": {
            "type": "string",
            "title": "Session Id",
            "description": "The session id the call that uses this token will have. Poll GET /v1/detect/sessions/{session_id} for its results."
          },
          "expires_at": {
            "type": "string",
            "title": "Expires At"
          },
          "expires_in": {
            "type": "integer",
            "title": "Expires In",
            "description": "Seconds until the token expires."
          },
          "record": {
            "type": "boolean",
            "title": "Record",
            "description": "Whether the call will be recorded."
          }
        },
        "type": "object",
        "required": [
          "request_id",
          "object",
          "token",
          "session_id",
          "expires_at",
          "expires_in",
          "record"
        ],
        "title": "StreamToken",
        "examples": [
          {
            "expires_at": "2026-10-19T09:35:00Z",
            "expires_in": 300,
            "object": "noesis.stream_token",
            "record": true,
            "request_id": "req_8f14e45fceea167a5a36dedd",
            "session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
            "token": "ntk_Q7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xQ7xa"
          }
        ]
      },
      "StreamTokenRequest": {
        "properties": {
          "record": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Record",
            "description": "Record the call (audio and transcript, kept 30 days). Default true; false turns recording off for the call that uses this token."
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "StreamTokenRequest",
        "examples": [
          {
            "record": false
          }
        ]
      },
      "StreamReadyEvent": {
        "type": "object",
        "description": "First message on the socket: the call is open.",
        "properties": {
          "type": {
            "const": "ready"
          },
          "session_id": {
            "type": "string"
          },
          "call_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "t": {
            "type": "number"
          },
          "audio": {
            "type": "object",
            "properties": {
              "encoding": {
                "type": "string"
              },
              "sample_rate": {
                "type": "integer"
              },
              "channels": {
                "type": "integer"
              }
            }
          }
        },
        "required": [
          "type",
          "session_id"
        ],
        "examples": [
          {
            "type": "ready",
            "session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
            "call_id": "CA0123",
            "t": 0.0,
            "audio": {
              "encoding": "mulaw",
              "sample_rate": 8000,
              "channels": 1
            }
          }
        ]
      },
      "StreamClassificationEvent": {
        "type": "object",
        "description": "The first answer and every change of it.",
        "properties": {
          "type": {},
          "seq": {},
          "version": {},
          "t": {},
          "label": {},
          "category": {},
          "confidence": {},
          "previous_label": {},
          "change": {},
          "trigger": {},
          "reason": {},
          "session_id": {},
          "call_id": {}
        },
        "required": [
          "type",
          "label"
        ],
        "examples": [
          {
            "type": "classification",
            "seq": 1,
            "version": 1,
            "t": 4.2,
            "label": "human",
            "category": "human",
            "confidence": 0.93,
            "previous_label": null,
            "change": "initial",
            "trigger": "speech",
            "reason": "A person answered and greeted the caller.",
            "session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
            "call_id": "CA0123"
          }
        ]
      },
      "StreamSummaryEvent": {
        "type": "object",
        "description": "After you send {\"type\":\"end\"} (or Twilio's stop): the final answer.",
        "properties": {
          "type": {},
          "session_id": {},
          "call_id": {},
          "t": {},
          "label": {},
          "category": {},
          "confidence": {},
          "decided": {},
          "timeline": {},
          "usage": {}
        },
        "required": [
          "type"
        ],
        "examples": [
          {
            "type": "summary",
            "session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
            "call_id": "CA0123",
            "t": 71.4,
            "label": "human",
            "category": "human",
            "confidence": 0.93,
            "decided": true,
            "timeline": [
              {
                "type": "classification",
                "seq": 1,
                "version": 1,
                "t": 4.2,
                "label": "human",
                "category": "human",
                "confidence": 0.93,
                "previous_label": null,
                "change": "initial",
                "trigger": "speech",
                "reason": "A person answered and greeted the caller."
              }
            ],
            "usage": {
              "audio_s": 71.4,
              "transcribed_s": 30.2,
              "checks": 4
            }
          }
        ]
      },
      "StreamUsageEvent": {
        "type": "object",
        "description": "Sent by the platform right after the summary (and at any other end of the call), before the socket closes: what was billed.",
        "properties": {
          "type": {
            "const": "usage"
          },
          "session_id": {
            "type": "string"
          },
          "operation_id": {
            "type": "string"
          },
          "billed_seconds": {
            "type": "number"
          },
          "credits_charged": {
            "type": "integer"
          }
        },
        "required": [
          "type",
          "billed_seconds"
        ],
        "examples": [
          {
            "type": "usage",
            "session_id": "5b2f0c9e8d7a4b6c9e1f2a3b4c5d6e7f",
            "operation_id": "op_6f1ed002ab5595859014ebf0951522d9",
            "billed_seconds": 71.4
          }
        ]
      },
      "StreamErrorEvent": {
        "type": "object",
        "description": "Sent before the socket closes with an error close code (4401, 4402, 4403, 4429, 1003, 1013, 1011), or for a problem that does not end the call (e.g. a frame that is too large).",
        "properties": {
          "type": {
            "const": "error"
          },
          "code": {
            "type": "string"
          },
          "error": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "request_id": {
            "type": "string"
          }
        },
        "required": [
          "type",
          "error",
          "status"
        ],
        "examples": [
          {
            "type": "error",
            "code": "insufficient_credits",
            "error": "Your balance is too low to continue this call; the call is being ended.",
            "status": 402,
            "request_id": "req_8f14e45fceea167a5a36dedd"
          }
        ]
      },
      "TwilioMediaMessage": {
        "type": "object",
        "description": "Twilio / Plivo media stream JSON, accepted as is. inbound = the person called; outbound = your agent (used for timing only, not billed).",
        "properties": {
          "event": {
            "enum": [
              "connected",
              "start",
              "media",
              "stop"
            ]
          },
          "start": {
            "type": "object"
          },
          "media": {
            "type": "object",
            "properties": {
              "payload": {
                "type": "string",
                "contentEncoding": "base64"
              },
              "track": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "event"
        ],
        "examples": [
          {
            "event": "media",
            "media": {
              "track": "inbound",
              "payload": "fn5+fn5+fn5+"
            }
          }
        ]
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "description": "Your API key in the Authorization header: `Authorization: Bearer dtf_live_...`. Create keys in the dashboard; each key has scopes that limit what it can do. Keys in URLs (`?api_key=`) are rejected.",
        "scheme": "bearer",
        "bearerFormat": "API key (dtf_live_...)"
      }
    }
  },
  "tags": [
    {
      "name": "Streams",
      "description": "Live calls over a WebSocket, and stream tokens for clients that cannot send headers."
    },
    {
      "name": "Recordings",
      "description": "Analyse a recorded call."
    },
    {
      "name": "Sessions",
      "description": "Results of your calls and file analyses, live and afterwards."
    },
    {
      "name": "Usage",
      "description": "Your Noesis calls and minutes."
    },
    {
      "name": "Health",
      "description": "Service status."
    }
  ],
  "x-websocket": {
    "url": "wss://api.noesis.detectif.ai/v1/detect/stream",
    "auth": [
      "Authorization: Bearer dtf_live_...",
      "X-API-Key",
      "?token=ntk_...",
      "Twilio <Parameter name=\"token\">"
    ],
    "client_messages": [
      "binary audio frames",
      "#/components/schemas/TwilioMediaMessage",
      "{\"type\":\"end\"}",
      "{\"type\":\"ping\"}",
      "{\"type\":\"start\",\"call_id\":\"...\"}"
    ],
    "server_messages": [
      "#/components/schemas/StreamReadyEvent",
      "#/components/schemas/StreamClassificationEvent",
      "#/components/schemas/StreamSummaryEvent",
      "#/components/schemas/StreamUsageEvent",
      "#/components/schemas/StreamErrorEvent"
    ],
    "close_codes": {
      "1000": "normal end",
      "1003": "invalid parameter",
      "1011": "error on our side",
      "1013": "busy, retry shortly",
      "4401": "authentication failed",
      "4402": "balance too low",
      "4403": "not allowed",
      "4429": "too many calls or requests"
    }
  }
}
