Install any skill in seconds. Free to start, no credit card required.
Get Started Free →End-to-end setup for making a Telnyx AI assistant call a phone number. Covers provisioning a phone number, creating a TeXML application, assigning the number, configuring telephony settings, whitelisting destination countries, and triggering outbound calls via scheduled events. Use this skill (not telnyx-ai-assistants-python) when the task involves an AI assistant placing, making, or triggering an outbound phone call to a user.
.claude/skills/team-telnyx-telnyx-ai-outbound-voice-python/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 32% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 38% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 125% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 81% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 44% | 0% |
Make an AI assistant call any phone number. This skill covers the complete setup from purchasing a number to triggering the call.
bashpip install telnyx requests
pythonimport os from telnyx import Telnyx client = Telnyx(api_key=os.environ.get("TELNYX_API_KEY"))
Outbound voice calls require all of the following. Missing any one produces a specific error — see Troubleshooting.
telephony_settings.default_texml_app_id set to the TeXML appModel availability varies by account. If client.ai.assistants.create() returns 422 "not available for inference", discover working models from existing assistants:
pythonfor a in client.ai.assistants.list().data: print(a.model)
Commonly available: openai/gpt-4o, Qwen/Qwen3-235B-A22B.
pythonimport time available = client.available_phone_numbers.list() phone = available.data[0].phone_number number_order = client.number_orders.create( phone_numbers=[{"phone_number": phone}], ) time.sleep(3) order = client.number_orders.retrieve(number_order.data.id) assert order.data.status == "success" print(f"Purchased: {phone}")
The voice_url is required by the API but is not used for outbound AI assistant calls. The TeXML app ID is also used as the connection_id when assigning phone numbers.
pythontexml_app = client.texml_applications.create( friendly_name="My AI Assistant App", voice_url="https://example.com/placeholder", ) app_id = texml_app.data.id # This is also the connection_id for phone number assignment
A phone number cannot make calls until it is assigned to a connection.
pythonimport requests requests.patch( f"https://api.telnyx.com/v2/phone_numbers/{phone}", headers={ "Authorization": f"Bearer {os.environ['TELNYX_API_KEY']}", "Content-Type": "application/json", }, json={"connection_id": app_id}, )
By default only US and CA are whitelisted. Calling any other country without whitelisting it first returns 403 error code D13.
pythonimport requests headers = { "Authorization": f"Bearer {os.environ['TELNYX_API_KEY']}", "Content-Type": "application/json", } # Find the outbound voice profile r = requests.get( "https://api.telnyx.com/v2/outbound_voice_profiles", headers=headers ) ovp_id = r.json()["data"][0]["id"] # Add destination countries (ISO 3166-1 alpha-2 codes) requests.patch( f"https://api.telnyx.com/v2/outbound_voice_profiles/{ovp_id}", headers=headers, json={"whitelisted_destinations": ["US", "CA", "IE", "GB"]}, ) # Assign the profile to the TeXML app requests.patch( f"https://api.telnyx.com/v2/texml_applications/{app_id}", headers=headers, json={ "friendly_name": "My AI Assistant App", "voice_url": "https://example.com/placeholder", "outbound": {"outbound_voice_profile_id": ovp_id}, }, )
telephony_settings with default_texml_app_id is required for outbound calls. Without it, scheduled_events.create() returns 400 "Assistant does not have telephony settings configured".
pythonassistant = client.ai.assistants.create( name="My Voice Assistant", model="openai/gpt-4o", instructions=( "You are a helpful phone assistant. " "Keep your answers concise and conversational since this is a phone call." ), greeting="Hello! How can I help you today?", telephony_settings={"default_texml_app_id": app_id}, )
To add telephony to an existing assistant:
pythonclient.ai.assistants.update( assistant_id="your-assistant-id", telephony_settings={"default_texml_app_id": app_id}, )
Use scheduled_events.create() with a time a few seconds in the future for an immediate call.
pythonfrom datetime import datetime, timezone, timedelta event = client.ai.assistants.scheduled_events.create( assistant_id=assistant.id, telnyx_conversation_channel="phone_call", telnyx_end_user_target="+13125550001", # Number to call (recipient) telnyx_agent_target=phone, # Your Telnyx number (caller ID) scheduled_at_fixed_datetime=( datetime.now(timezone.utc) + timedelta(seconds=5) ).isoformat(), ) print(f"Status: {event.status}") # "pending"
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | assistant_id | string (UUID) | Yes | The AI assistant that handles the call. | | telnyx_conversation_channel | string | Yes | Must be "phone_call". | | telnyx_end_user_target | string (E.164) | Yes | Phone number to call (recipient). | | telnyx_agent_target | string (E.164) | Yes | Your Telnyx number (caller ID). Must be assigned to the TeXML app. | | scheduled_at_fixed_datetime | string (ISO 8601) | Yes | When to place the call. ~5s in the future for immediate. | | dynamic_variables | object | No | Variables to pass to the assistant. | | conversation_metadata | object | No | Metadata to attach to the conversation. |
pythonimport os, time from datetime import datetime, timezone, timedelta from telnyx import Telnyx import requests api_key = os.environ["TELNYX_API_KEY"] client = Telnyx(api_key=api_key) headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} # 1. Buy a number available = client.available_phone_numbers.list() phone = available.data[0].phone_number order = client.number_orders.create(phone_numbers=[{"phone_number": phone}]) time.sleep(3) # 2. Create TeXML app app = client.texml_applications.create( friendly_name="AI Outbound App", voice_url="https://example.com/placeholder", ) app_id = app.data.id # 3. Assign number requests.patch( f"https://api.telnyx.com/v2/phone_numbers/{phone}", headers=headers, json={"connection_id": app_id}, ) # 4. Configure outbound profile ovp = requests.get("https://api.telnyx.com/v2/outbound_voice_profiles", headers=headers).json()["data"][0] requests.patch( f"https://api.telnyx.com/v2/outbound_voice_profiles/{ovp['id']}", headers=headers, json={"whitelisted_destinations": ["US", "CA"]}, ) requests.patch( f"https://api.telnyx.com/v2/texml_applications/{app_id}", headers=headers, json={ "friendly_name": "AI Outbound App", "voice_url": "https://example.com/placeholder", "outbound": {"outbound_voice_profile_id": ovp["id"]}, }, ) # 5. Create assistant with telephony assistant = client.ai.assistants.create( name="Outbound Bot", model="openai/gpt-4o", instructions="You are a helpful phone assistant.", telephony_settings={"default_texml_app_id": app_id}, ) # 6. Trigger call client.ai.assistants.scheduled_events.create( assistant_id=assistant.id, telnyx_conversation_channel="phone_call", telnyx_end_user_target="+13125550001", telnyx_agent_target=phone, scheduled_at_fixed_datetime=(datetime.now(timezone.utc) + timedelta(seconds=5)).isoformat(), )
The assistant is missing:
pythontelephony_settings={"default_texml_app_id": app_id}
Fix by updating the assistant with default_texml_app_id.
The TeXML application does not have an outbound voice profile assigned.
Fix Step 4 above: patch the TeXML app with:
python"outbound": {"outbound_voice_profile_id": ovp_id}
detail.code == "D13"The destination country is not whitelisted on the outbound voice profile.
Fix Step 4 above: add the destination country ISO code to whitelisted_destinations.
Check:
scheduled_at_fixed_datetime is in the future and in UTCtelnyx_agent_target is your purchased Telnyx numbertelnyx_end_user_target is the recipient numberThe selected model is not enabled for your account.
List existing assistants to discover working models:
pythonfor a in client.ai.assistants.list().data: print(a.model)
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 19,118 | 10,628 | -44% | 1 | 1 | 0% | 3,637 | 4,799 | +32% | 0 | 0 | — |
case-02 | fail→pass | 16,800 | 8,732 | -48% | 1 | 1 | 0% | 3,262 | 4,496 | +38% | 0 | 0 | — |
case-03 | fail→pass | 12,610 | 12,310 | -2% | 1 | 1 | 0% | 2,277 | 5,132 | +125% | 0 | 0 | — |
case-04 | pass→pass | 14,027 | 10,993 | -22% | 1 | 1 | 0% | 2,646 | 4,690 | +77% | 0 | 0 | — |
case-05 | pass→pass | 12,264 | 13,767 | +12% | 1 | 1 | 0% | 2,382 | 5,429 | +128% | 0 | 0 | — |
case-06 | pass→pass | 19,284 | 15,793 | -18% | 1 | 1 | 0% | 3,901 | 6,056 | +55% | 0 | 0 | — |
case-07 | pass→pass | 7,663 | 4,918 | -36% | 1 | 1 | 0% | 1,467 | 3,576 | +144% | 0 | 0 | — |
case-08 | pass→pass | 14,069 | 5,331 | -62% | 1 | 1 | 0% | 2,693 | 3,625 | +35% | 0 | 0 | — |
case-09 | pass→pass | 9,100 | 3,916 | -57% | 1 | 1 | 0% | 1,811 | 3,384 | +87% | 0 | 0 | — |
case-10 | fail→pass | 12,923 | 7,725 | -40% | 1 | 1 | 0% | 2,349 | 4,255 | +81% | 0 | 0 | — |
case-11 | fail→pass | 12,751 | 3,501 | -73% | 1 | 1 | 0% | 2,267 | 3,273 | +44% | 0 | 0 | — |
case-12 | fail→pass | 13,864 | 7,907 | -43% | 1 | 1 | 0% | 2,485 | 4,256 | +71% | 0 | 0 | — |
case-13 | fail→pass | 6,860 | 2,512 | -63% | 1 | 1 | 0% | 1,126 | 3,047 | +171% | 0 | 0 | — |
case-14 | fail→pass | 10,737 | 3,447 | -68% | 1 | 1 | 0% | 1,803 | 3,218 | +78% | 0 | 0 | — |
case-15 | pass→pass | 9,097 | 4,860 | -47% | 1 | 1 | 0% | 1,609 | 3,511 | +118% | 0 | 0 | — |
case-16 | fail→pass | 11,234 | 4,155 | -63% | 1 | 1 | 0% | 2,067 | 3,413 | +65% | 0 | 0 | — |
case-17 | fail→pass | 10,608 | 6,656 | -37% | 1 | 1 | 0% | 1,935 | 4,050 | +109% | 0 | 0 | — |
case-18 | fail→pass | 11,451 | 4,971 | -57% | 1 | 1 | 0% | 1,864 | 3,533 | +90% | 0 | 0 | — |
case-19 | fail→pass | 13,778 | 5,242 | -62% | 1 | 1 | 0% | 2,068 | 3,634 | +76% | 0 | 0 | — |
case-20 | pass→pass | 12,216 | 2,186 | -82% | 1 | 1 | 0% | 1,997 | 2,992 | +50% | 0 | 0 | — |
case-21 | fail→pass | 9,495 | 6,045 | -36% | 1 | 1 | 0% | 1,866 | 3,918 | +110% | 0 | 0 | — |
case-22 | fail→pass | 21,990 | 18,388 | -16% | 1 | 1 | 0% | 4,574 | 6,030 | +32% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +64 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.