> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pyai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Answering machine detection (AMD)

> Detect who or what answered a call, human, voicemail, IVR, iPhone/Google screening, dead number, fax, with a one-line-TwiML Twilio drop-in and an explainable reason.

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.

<Info>
  **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.
</Info>

## 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:

```xml theme={null}
<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:

```json theme={null}
{
  "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"
}
```

| field                | meaning                                                                                                                                                                                                     |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `answered_by`        | PyAI'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_twilio` | Twilio's exact `AnsweredBy` enum, so a drop-in keeps its routing logic unchanged                                                                                                                            |
| `confidence`         | 0-1                                                                                                                                                                                                         |
| `decision_ms`        | latency from answer to decision                                                                                                                                                                             |
| `reason`             | the 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

<CodeGroup>
  ```ts TypeScript theme={null}
  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);
  ```

  ```python Python theme={null}
  from pyai import PyAI

  pyai = PyAI(api_key="pyai_live_...")

  pyai.amd.config.set(aggressiveness=0.25, webhook_url="https://you/amd-events")

  decision = pyai.amd.calls.get("C_123")
  print(decision["answered_by"], decision["answered_by_twilio"], decision["reason"])
  ```

  ```bash cURL theme={null}
  # Set the operating point + webhook.
  curl -X POST https://api.pyai.com/v1/amd/config \
    -H "Authorization: Bearer $PYAI_KEY" \
    -H "Content-Type: application/json" \
    -d '{"aggressiveness":0.25,"webhook_url":"https://you/amd-events"}'

  # Read a decision after the call.
  curl https://api.pyai.com/v1/amd/calls/C_123 \
    -H "Authorization: Bearer $PYAI_KEY"
  ```
</CodeGroup>

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

## Related

* [Build a phone voice agent with Twilio](/guides/twilio-voice-agent)
* [Omni protocol](/realtime/omni-protocol)
* [Pricing & metering](/pricing-and-metering)
