Answering-machine detection (WebSocket)
Realtime answering-machine detection over a WebSocket. This surface speaks Twilio’s Media Streams protocol natively (start / media / stop frames, G.711 μ-law 8 kHz base64, ~20 ms), so migrating from Twilio AMD is a one-line-TwiML change, point the call’s media at PyAI, keep your carrier and your code.
<Response><Connect>
<Stream url="wss://api.pyai.com/v1/amd/stream">
<Parameter name="api_key" value="YOUR_PYAI_KEY"/>
<Parameter name="aggressiveness" value="0.25"/>
<Parameter name="webhook" value="https://you/amd-events"/>
</Stream>
</Connect></Response>
From Twilio, authenticate with the api_key <Parameter> shown above (Twilio strips query strings from the <Stream> URL and cannot send headers; PyAI verifies the key from the stream’s start frame before processing any audio, and closes connections that never present a valid key). Server-side clients may instead authenticate at the handshake with the Sec-WebSocket-Protocol: pyai-key.<API_KEY> subprotocol or ?api_key=. Requires the amd:detect scope. Mid-call, PyAI pushes an amd decision event on the socket (and to the per-call TwiML webhook parameter): answered_by (the routing class: human, machine, sit_invalid, unknown), answered_by_twilio (Twilio’s exact AnsweredBy enum for drop-in routing parity), confidence, decision_ms, and a human-readable reason. A machine decision’s subtype (voicemail, ivr, screening, music) is stored on the call record rather than pushed on the wire: read it with GET /v1/amd/calls/{id} or receive it on the account-wide amd.call.completed webhook (webhook_url in POST /v1/amd/config). The per-call aggressiveness <Parameter> overrides the account default from POST /v1/amd/config.
Billed per answered call (amd.calls), the first 5,000 answered calls/month are free, then $0.004/answered call; AMD bundled with PyAI telephony/Omni is included.
Authorizations
Use Authorization: Bearer pyai_live_... (or pyai_test_...).
Response
WebSocket upgrade, Twilio Media Streams protocol; PyAI emits amd decision events.