Python SDK
dialgem: sync and asyncio clients for your backend, with typed responses and webhook verification.
dialgem is for your backend. It needs Python 3.10+ and three dependencies: httpx, pydantic and typing_extensions. Dialgem and AsyncDialgem expose the same methods.
Install
pip install dialgemCreate an organization API key in the dashboard (see Get your API keys) and set it as DIALGEM_API_KEY.
from dialgem import Dialgem
dialgem = Dialgem() # reads DIALGEM_API_KEY
agent = dialgem.agents.create(
name="Front desk",
system_prompt="You are the front desk for {{business_name}}.",
first_message="Thanks for calling, how can I help?",
default_dynamic_variables={"business_name": "Acme Dental"},
)Options
| Option | Default | Meaning |
|---|---|---|
api_key | $DIALGEM_API_KEY | Your organization API key. |
timeout | 60 | Seconds per attempt. |
max_retries | 2 | See Retries. |
default_headers | {} | Sent with every request. |
http_client | a new httpx.Client | Your own client, for a proxy or mTLS. You close it. |
Every method takes request_options={"timeout": 10, "max_retries": 0, "headers": {...}} for one request. Field names are the API's own snake_case, passed as keyword arguments.
Async
import asyncio
from dialgem import AsyncDialgem
async def main():
async with AsyncDialgem() as dialgem:
agent = await dialgem.agents.create(name="Front desk")
async for call in dialgem.calls.list(limit=50): # walks every page
print(call.id, call.call_status)
asyncio.run(main())Recipes
Put an agent on a phone number
number = dialgem.phone_numbers.create(phone_number="+14155550100")
dialgem.phone_numbers.update(
number.id,
inbound_agent_id=agent.id,
set_inbound_agent=True, # inbound_agent_id is ignored without this
)Place an outbound call
call = dialgem.calls.create_phone_call(
to="+14155550123",
from_="+14155550100", # `from_` is the wire name; must be a number on your organization
agent_id=agent.id,
metadata={"order": "A-1042"},
dialgem_llm_dynamic_variables={"customer_name": "Ada"},
)create_phone_call is a POST and is not retried, so an unclear failure cannot ring the person twice.
Start a web call for a browser
dialgem.web_calls.update_settings(allowed_origins=["https://app.example.com"]) # once
web_call = dialgem.web_calls.create(agent_id="AGENT_ID", metadata={"visitor": "v_123"})
return {"access_token": web_call.access_token} # 60 seconds, single useThe page connects with @dialgem/client. See Web calls.
List calls
for call in dialgem.calls.list(agent_id=agent.id, start_after=1730000000000, limit=50):
print(call.id, call.transcript_text)
page = dialgem.calls.list(limit=50) # or one page at a time
page.data, page.has_next_page(), page.get_next_page()Verify webhooks
Verify against the raw request body. A body that has been parsed and written back out will not match.
from dialgem import webhooks, WebhookVerificationError
@app.post("/dialgem")
async def hook(request: Request):
body = await request.body()
try:
event = webhooks.construct_event(body, request.headers.get("x-dialgem-signature"), SECRET)
except WebhookVerificationError:
return Response(status_code=400)
if event.event == "call_ended":
print(event.call.id, event.call.duration_ms)
return Response(status_code=204)construct_function_call does the same for requests to your functions. Signatures older than five minutes are rejected; change that with tolerance_ms=. See the event reference.
Live call control
dialgem.sessions.update(call.id, dynamic_variables={"plan": "pro"}) # this call only
dialgem.sessions.say(call.id, text="One moment while I check that.")
dialgem.sessions.release(call.id)say and release are off unless live control is enabled for your account, and then raise PermissionDeniedError with err_code == "live_control_disabled".
Errors
from dialgem import NotFoundError, APIError, APIConnectionError
try:
dialgem.agents.retrieve("nope")
except NotFoundError as e:
print(e.status, e.err_code, e.request_id) # quote request_id to support
except APIError as e:
print(e.status, e.issues) # for example 422 validation issues
except APIConnectionError:
... # no response400 BadRequestError, 401 AuthenticationError, 403 PermissionDeniedError, 404 NotFoundError, 409 ConflictError, 422 UnprocessableEntityError, 429 RateLimitError, 5xx InternalServerError. All derive from APIError, then DialgemError.
Retries
GET, PUT, PATCH and DELETE are retried up to max_retries times on 408, 429, 5xx and connection errors, with exponential backoff and jitter, honouring Retry-After (capped at 60 seconds). POST is never retried unless you pass request_options={"max_retries": n}. A 409 and file uploads are never retried.
Responses
Responses are pydantic models. Unknown fields are kept as attributes and in model.model_extra, and model.to_dict() returns the JSON as sent. If the API returns something that does not fit the model, for example a field changed type, the SDK returns it unvalidated and issues one UserWarning per model instead of raising, so an API change does not take your integration down. Upgrade the SDK when you see the warning.