← Twilio Developer KitCONTENT HISTORY

Update to Twilio Developer Kit

Snapshot Sep 30, 2026 · 22:50 UTC · version 0.2.2

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "twilio-voice-outbound-calls",
  "description": "Make outbound phone calls via Twilio's Programmable Voice REST API. Covers the full voice platform: calls.create(), answering machine detection (AMD), conference-based agent bridging, call recording, status tracking, and SIP Trunking. Use this skill for outbound calls, sales dialers, or when asking what voice APIs are available.",
  "included_files": [
    {
      "relative_path": "agents/openai.yaml",
      "size_in_bytes": 235
    }
  ],
  "skill_md_contents": "---\nname: twilio-voice-outbound-calls\ndescription: >\n  Make outbound phone calls via Twilio's Programmable Voice REST API. Covers\n  the full voice platform: calls.create(), answering machine detection (AMD),\n  conference-based agent bridging, call recording, status tracking, and SIP\n  Trunking. Use this skill for outbound calls, sales dialers, or when asking\n  what voice APIs are available.\n---\n\n## Overview\n\n> **Agent safety:** Before placing an outbound call, always confirm the recipient number and intent with the user. Outbound calls are irreversible and may incur charges. For automated systems, implement TCPA compliance: obtain prior express consent, respect quiet hours (8 AM–9 PM recipient local time), and maintain a Do Not Call list.\n\nEvery outbound call requires a `from` Twilio number, a `to` recipient, and TwiML instructions that define what happens when the call is answered — either as a webhook URL or inline.\n\n---\n\n## Voice Platform Capabilities\n\nOutbound calls go beyond basic `calls.create()`. Here's what you can build:\n\n| Capability | How | When to use |\n|-----------|-----|-------------|\n| **Basic outbound call** | `calls.create()` with TwiML or webhook URL | Any outbound call — see Quickstart below |\n| **Answering Machine Detection (AMD)** | `machineDetection` parameter on `calls.create()` | Sales dialers, call campaigns — filter voicemail from humans |\n| **Conference-based agent bridging** | `<Dial><Conference>` in TwiML | Connect agents to live prospects with whisper, barge, hold |\n| **SIP Trunking** | Elastic SIP Trunking | Bring your own carrier for outbound calls — cost reduction at scale |\n| **Call Recording** | `record=True` on `calls.create()` or `<Record>` verb | Compliance, QA, training |\n| **Voice Insights** | Automatic per-call metrics | Call quality monitoring — latency, jitter, packet loss, answer rates |\n| **AI Voice Agents** | ConversationRelay + LLM | Real-time speech recognition → LLM → TTS for conversational AI |\n\nFor TwiML verbs (Say, Gather, Dial, Record, Conference, Pay), see `twilio-voice-twiml`.\nFor AI voice agents, see `twilio-voice-conversation-relay`.\n\n---\n\n## Prerequisites\n\n- Twilio account with a voice-capable phone number\n  — New to Twilio? See `twilio-account-setup` for signup, getting a number, and trial limitations\n  — Trial accounts can only call verified numbers\n- Environment variables:\n  - `TWILIO_ACCOUNT_SID`\n  - `TWILIO_AUTH_TOKEN`\n  — See `twilio-iam-auth-setup` for credential setup and best practices\n- SDK: `pip install twilio` / `npm install twilio`\n\n---\n\n## Quickstart\n\n**Python**\n```python\nimport os\nfrom twilio.rest import Client\n\nclient = Client(os.environ[\"TWILIO_ACCOUNT_SID\"], os.environ[\"TWILIO_AUTH_TOKEN\"])\n\ncall = client.calls.create(\n    from_=\"+15017122661\",    # Your Twilio number (E.164)\n    to=\"+15558675310\",       # Recipient (E.164)\n    twiml=\"<Response><Say>Your order has shipped. Goodbye.</Say></Response>\"\n)\n\nprint(call.sid)     # CAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nprint(call.status)  # queued | ringing | in-progress | completed | failed\n```\n\n**Node.js**\n```node\nconst twilio = require(\"twilio\");\nconst client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);\n\nconst call = await client.calls.create({\n    from: \"+15017122661\",\n    to: \"+15558675310\",\n    twiml: \"<Response><Say>Your order has shipped. Goodbye.</Say></Response>\",\n});\n\nconsole.log(call.sid);\nconsole.log(call.status);\n```\n\n---\n\n## Key Patterns\n\n### Use a Webhook URL for Dynamic Call Handling\n\nPass a `url` instead of inline `twiml` — Twilio POSTs to your server when the call connects and executes the TwiML you return.\n\n**Python**\n```python\ncall = client.calls.create(\n    from_=\"+15017122661\",\n    to=\"+15558675310\",\n    url=\"https://yourapp.com/twiml/welcome\"\n)\n```\n\n**Node.js**\n```node\nconst call = await client.calls.create({\n    from: \"+15017122661\",\n    to: \"+15558675310\",\n    url: \"https://yourapp.com/twiml/welcome\",\n});\n```\n\n**Python (Flask) — TwiML webhook handler**\n```python\nfrom flask import Flask\nfrom twilio.twiml.voice_response import VoiceResponse\n\napp = Flask(__name__)\n\n@app.route(\"/twiml/welcome\", methods=[\"POST\"])\ndef welcome():\n    response = VoiceResponse()\n    response.say(\"Hello! Press 1 to hear your account balance.\")\n    response.gather(num_digits=1, action=\"/twiml/handle-input\")\n    return str(response)\n```\n\n**Node.js (Express) — TwiML webhook handler**\n```node\nconst { VoiceResponse } = require(\"twilio\").twiml;\n\napp.post(\"/twiml/welcome\", (req, res) => {\n    const response = new VoiceResponse();\n    response.say(\"Hello! Press 1 to hear your account balance.\");\n    response.gather({ numDigits: 1, action: \"/twiml/handle-input\" });\n    res.type(\"text/xml\").send(response.toString());\n});\n```\n\nFor all TwiML verbs (Say, Gather, Dial, Record, Conference), see `twilio-voice-twiml`.\n\n### Track Call Status\n\n**Python**\n```python\ncall = client.calls.create(\n    from_=\"+15017122661\",\n    to=\"+15558675310\",\n    url=\"https://yourapp.com/twiml/welcome\",\n    status_callback=\"https://yourapp.com/call-status\",\n    status_callback_method=\"POST\"\n)\n```\n\n**Node.js**\n```node\nconst call = await client.calls.create({\n    from: \"+15017122661\",\n    to: \"+15558675310\",\n    url: \"https://yourapp.com/twiml/welcome\",\n    statusCallback: \"https://yourapp.com/call-status\",\n    statusCallbackMethod: \"POST\",\n});\n```\n\nStatus transitions: `queued → ringing → in-progress → completed` (or `failed`/`busy`/`no-answer`).\n\n### Record a Call\n\n**Python**\n```python\ncall = client.calls.create(\n    from_=\"+15017122661\",\n    to=\"+15558675310\",\n    url=\"https://yourapp.com/twiml/welcome\",\n    record=True,\n    recording_status_callback=\"https://yourapp.com/recording-ready\"\n)\n```\n\n**Node.js**\n```node\nconst call = await client.calls.create({\n    from: \"+15017122661\",\n    to: \"+15558675310\",\n    url: \"https://yourapp.com/twiml/welcome\",\n    record: true,\n    recordingStatusCallback: \"https://yourapp.com/recording-ready\",\n});\n```\n\nRecordings accessible at `https://api.twilio.com/2010-04-01/Accounts/{AccountSid}/Recordings`.\n\n---\n\n## Answering Machine Detection (AMD)\n\nDetect whether a human or voicemail answers before connecting an agent or leaving a message. Two modes:\n\n| Mode | Behavior | Best for |\n|------|----------|----------|\n| `Enable` | Returns result immediately when human/machine first detected | **Sales dialers** — connect agent to humans ASAP |\n| `DetectMessageEnd` | Waits for voicemail greeting to finish (beep/silence) | **Leaving voicemail** — wait for beep, then play/record message |\n\n**Python — Sales dialer (connect agents to live answers only)**\n```python\ncall = client.calls.create(\n    from_=\"+15017122661\",\n    to=\"+15558675310\",\n    url=\"https://yourapp.com/handle-answer\",\n    machine_detection=\"Enable\",  # Immediate detection — use for live-agent connect\n    async_amd=True,              # Non-blocking — call connects while AMD analyzes\n    async_amd_status_callback=\"https://yourapp.com/amd-result\",\n    async_amd_status_callback_method=\"POST\"\n)\n```\n\n**Node.js**\n```javascript\nconst call = await client.calls.create({\n    from: \"+15017122661\",\n    to: \"+15558675310\",\n    url: \"https://yourapp.com/handle-answer\",\n    machineDetection: \"Enable\",\n    asyncAmd: true,\n    asyncAmdStatusCallback: \"https://yourapp.com/amd-result\",\n    asyncAmdStatusCallbackMethod: \"POST\",\n});\n```\n\n**AMD webhook delivers `AnsweredBy` parameter:**\n\n| Value | Meaning | Action |\n|-------|---------|--------|\n| `human` | Live person detected | Connect to agent |\n| `machine_start` | Machine detected, greeting still playing | Hang up or wait |\n| `machine_end_beep` | Voicemail beep heard | Leave message |\n| `machine_end_silence` | Silence after greeting | Leave message |\n| `fax` | Fax tone detected | Hang up |\n| `unknown` | Could not determine | Treat as human or retry |\n\n**Conference-based agent bridging** (production pattern for sales dialers):\n\n```python\n# In /handle-answer webhook — when AMD says \"human\":\nresponse = VoiceResponse()\ndial = response.dial()\ndial.conference(\n    f\"Sales-{call_sid}\",\n    start_conference_on_enter=False,  # Prospect waits for agent\n    end_conference_on_exit=True\n)\n\n# Separately, dial agent into same conference:\nclient.calls.create(\n    from_=\"+15017122661\",\n    to=agent_number,\n    twiml=f'<Response><Dial><Conference startConferenceOnEnter=\"true\">Sales-{call_sid}</Conference></Dial></Response>'\n)\n```\n\nThis pattern gives you call control (whisper to agent before connecting, supervisor barge-in, hold) that direct `<Dial>` does not.\n\n**Cost**: AMD adds ~$0.0075 per call to standard voice pricing.\n\n---\n\n## Response Fields\n\n| Field | Description |\n|-------|-------------|\n| `sid` | Call identifier (`CA...`) |\n| `status` | `queued`, `ringing`, `in-progress`, `completed`, `failed`, `busy`, `no-answer` |\n| `duration` | Length in seconds (after completion) |\n| `price` | Cost (populated after completion) |\n\n---\n\n## Common Errors\n\n| Code | Meaning | Fix |\n|------|---------|-----|\n| 21211 | Invalid `to` number | Validate E.164 format |\n| 13224 | Invalid TwiML at webhook URL | Validate TwiML response from your server |\n| 13225 | TwiML URL returned non-200 status | Fix your webhook endpoint |\n\n---\n\n## CANNOT\n\n- **Cannot use a private TwiML URL** — Must be publicly accessible. Use ngrok for local development only; deploy to a cloud provider for production.\n- **Cannot exceed ~4,096 characters in inline `twiml` parameter** — Use a TwiML URL for longer responses\n- **Cannot call unverified numbers on trial accounts** — Upgrade to paid or verify recipient numbers first\n- **AMD accuracy is ~85-90%** — Expect some false positives/negatives. Tune `machineDetectionSpeechThreshold` (default 2400ms) based on your results.\n- **AMD adds latency** — `Enable` mode returns results faster but may misclassify short greetings. `DetectMessageEnd` is more accurate but adds seconds before your app can act.\n- **Cannot use AMD with SIP Trunking** — AMD is only available on calls made via the Calls API\n\n---\n\n## Next Steps\n\n- **TwiML verb reference (Say, Gather, Dial, Record, Conference, Pay):** `twilio-voice-twiml`\n- **AI voice agents with real-time speech/LLM:** `twilio-voice-conversation-relay`\n- **Improve answer rates (Branded Calling, STIR/SHAKEN):** `twilio-numbers-senders`\n"
}

SHA-256: f136d279092295f370487c90d0e5744654ce634388dcddfa2bb2d4211c0bde21