Skip to main content
PyAI AMD tells your dialer who or what answered a call, a person, a voicemail, an IVR menu, a live receptionist, an iPhone/Google screening assistant, a dead number, or a fax, in a fraction of Twilio’s dead-air dwell, 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:
<Response>
  <Connect>
    <Stream url="wss://api.pyai.com/v1/amd/stream?api_key=YOUR_PYAI_KEY">
      <Parameter name="aggressiveness" value="0.25"/>
      <Parameter name="webhook"        value="https://you/amd-events"/>
    </Stream>
  </Connect>
</Response>
Twilio cannot set headers or WS subprotocols on <Stream>, so the key rides the URL as ?api_key= (the standard server-side WS auth). Your own server-side clients may instead use the Sec-WebSocket-Protocol: pyai-key.<API_KEY> subprotocol.

The decision

PyAI pushes an amd event mid-call (and to your webhook) so a predictive dialer can route or drop instantly:
{
  "event": "amd",
  "call_id": "C_123",
  "answered_by": "voicemail",
  "answered_by_twilio": "machine_start",
  "confidence": 0.96,
  "decision_ms": 720,
  "reason": "machine phrase: 'please leave a message' at 1.2s"
}
fieldmeaning
answered_byPyAI’s richer vocabulary: human, voicemail, live_voicemail, screening (iPhone/Google Call Screen), ivr, human_gatekeeper, sit_invalid (dead/disconnected number), fax, silence, unknown
answered_by_twilioTwilio’s exact AnsweredBy enum, so a drop-in keeps its routing logic unchanged
confidence0-1
decision_mslatency from answer to decision
reasonthe word-level evidence, something Twilio can’t give you
Read it back later with GET /v1/amd/calls/{id}.

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

import PyAI from "@pyai/sdk";

const pyai = new PyAI({ apiKey: process.env.PYAI_KEY });

// Account default operating point + webhook (per-call TwiML overrides it).
await pyai.amd.config.set({ aggressiveness: 0.25, webhookUrl: "https://you/amd-events" });

// After the call, read the decision.
const decision = await pyai.amd.calls.get("C_123");
console.log(decision.answered_by, decision.answered_by_twilio, decision.reason);

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. Because AMD decides in a fraction of the incumbent’s dead-air dwell, you also pay for fewer carrier call-seconds.