Skip to main content
PyAI AMD tells your dialer who or what answered a call, a person, a voicemail, an IVR menu, an iPhone/Google screening assistant, or a dead number, and it hands you the reason it decided. If you’re already on Twilio, turning it on is one line of TwiML.
Endpoint: wss://api.pyai.com/v1/amd/stream (scope amd:detect). Config: POST /v1/amd/config (scope amd:configure). Reads: GET /v1/amd/calls, GET /v1/amd/calls/{id} (scope amd:read). Billed per answered call, the first 5,000 answered calls/month are free, then $0.004/call; free when bundled with PyAI telephony or Omni.

The wedge: a zero-code Twilio migration

AMD speaks Twilio’s Media Streams protocol natively (start / media / stop frames, G.711 μ-law 8 kHz base64), so a customer already on Twilio adds one line of TwiML, no carrier change, no new SDK, to fork the call’s media to PyAI:
Twilio cannot set headers, WS subprotocols, or even URL query parameters on <Stream> (the query string is stripped before connecting), so the key travels as a <Parameter>: it arrives in the stream’s start frame and PyAI verifies it before processing any audio. A connection that never presents a valid key is closed within seconds. Your own server-side clients (not Twilio) can authenticate at the handshake instead, with ?api_key= on the URL or the Sec-WebSocket-Protocol: pyai-key.<API_KEY> subprotocol.

The decision

PyAI pushes an amd event mid-call (and to the per-call TwiML webhook, if you set one) so a predictive dialer can route or drop instantly:
A machine decision is further classified by subtype, voicemail, ivr, screening (iPhone/Google Call Screen), or music (hold music). The subtype is stored on the call record rather than pushed on the wire: read it back with GET /v1/amd/calls/{id} (or on the amd.call.completed webhook below), where answered_by is one of human, machine, voicemail, ivr, screening, music, sit_invalid, unknown.

Webhooks

Two webhook paths, carrying different payloads:
  • Per-call, the TwiML <Parameter name="webhook">. The moment the decision lands, PyAI POSTs the wire event above (the coarse answered_by class) to that URL, for that call only.
  • Account-wide, webhook_url in POST /v1/amd/config. When the call completes, PyAI POSTs a signed amd.call.completed event with the full call record, including the machine subtype in answered_by:
Most dialers ignore the socket entirely and drive everything from webhooks.

The one dial: aggressiveness

AMD has a single operating-point dial, aggressiveness ∈ [0, 1], set per account (POST /v1/amd/config) or per call (a TwiML <Parameter>):
  • 0.0-0.25, human-safe (default). Never hang up on a person. For predictive dialers with live agents; on the deadline it returns unknown (let the agent listen) rather than risk a false machine.
  • 0.6-1.0, machine-aggressive. Fire machine fast, for AI voicemail-drop bots.
The error costs are asymmetric, a false machine (hang up on a prospect) is far worse than a false human (waste a few agent-seconds), so you pick your point on the curve instead of living with one fixed default.

Set it up with the SDK

Two ways to turn it on

  1. Twilio drop-in, the TwiML above. Keep your carrier and your code; the answered_by_twilio field speaks Twilio’s exact vocabulary.
  2. Native, a flag on any call already running on PyAI telephony or Omni. Bundled AMD is included at no charge.

Billing

Billed per answered call (amd.calls): no-answers, busies, and failed calls are free, the same basis as Twilio. The first 5,000 answered calls each month are free (a self-serve “get started free” tier), then $0.004/answered call. AMD bundled with PyAI telephony or Omni is included. A fast decision also means fewer dead-air carrier seconds on every answered call.