Python SDK

View as Markdown

The dial-sdk package is an async client for Python 3.11+.

Install

$pip install dial-sdk

Construct a client

1from dial_sdk import DialClient
2
3dial = DialClient(api_key="sk_live_...")
4# Optional: DialClient(api_key="sk_live_...", base_url="https://api.getdial.ai")

The client is async; call its methods with await inside an event loop.

Core operations

1# List your numbers
2numbers = await dial.list_numbers()
3
4# Send an SMS
5message = await dial.send_message(
6 to="+14155550123",
7 from_number_id=numbers[0].id,
8 body="Hello from Dial",
9)
10
11# Place an AI voice call
12call = await dial.make_call(
13 to="+14155550123",
14 from_number_id=numbers[0].id,
15 outbound_instruction="You are confirming a reservation.",
16 language="en-US",
17)
18
19# Reply or react to a message (from list_messages or a message.received event)
20reply = await dial.reply_to_message(message.id, body="On my way!")
21reaction = await dial.reply_to_message(message.id, reaction="πŸ”₯")
22
23# History
24recent_messages = await dial.list_messages()
25recent_calls = await dial.list_calls()
26
27# Update a number's properties (any subset)
28await dial.set_number_properties(
29 numbers[0].id,
30 nickname="Support line",
31 inbound_instruction="You are ACME's receptionist.",
32)
33
34await dial.close()

send_message and make_call choose the from-number with exactly one of from_number (a flexible reference β€” phone number ID, one of your numbers in E.164, or a nickname) or from_number_id (ID only).

Typing indicators

Show a typing indicator while composing. iMessage numbers display it; standard (SMS) numbers have no typing concept and silently ignore it, so the calls are safe unconditionally.

1# Primitives
2await dial.start_typing(to_number="+14155550123", from_number="Support line")
3await dial.stop_typing(to_number="+14155550123", from_number="Support line")
4
5# Or scope it to a block: starts on enter, stops on exit (even on error)
6async with dial.typing(to_number="+14155550123", from_number="Support line") as session:
7 await session.send_message(body="Working on it…") # to/from prefilled

Delivering a message or reaction clears the indicator natively on the recipient’s device. The session compensates: after each session.send_message it automatically re-starts the indicator, so inside the block the bubble persists β€” the only real stop is __aexit__ (which runs even when the block raises). With the bare primitives, remember that a send clears it: call start_typing again to keep composing, and stop_typing when you stop without sending. There is no keep-alive, so a stale indicator may also clear on its own during a very long pause.

The client makes a single attempt per call β€” it never auto-retries. MakeCallParams accepts an optional idempotency_key: pass the same key on each retry and a request that actually succeeded returns the original call instead of dialing again. send_message has no idempotency key β€” wrapping it in a retry can send a duplicate. See Retries and idempotency.

Stream events

new_events_connection() is an async context manager. Use async with so the connection closes cleanly.

1async with dial.new_events_connection() as conn:
2 async for event in conn:
3 if event["type"] == "message.received":
4 print("inbound:", event["data"]["from"], event["data"]["body"])

If you can’t use async with, call await conn.close() yourself.

The stream is presence-based, not at-least-once β€” missed events replay only if you reconnect within 2 minutes. For durable, off-machine delivery, register a webhook β€” signed and retried at-least-once.

Types

Methods return typed PhoneNumber, Message, and Call dataclasses. See Core concepts for field meanings.