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

bash
pip install dialgem

Create an organization API key in the dashboard (see Get your API keys) and set it as DIALGEM_API_KEY.

python
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

OptionDefaultMeaning
api_key$DIALGEM_API_KEYYour organization API key.
timeout60Seconds per attempt.
max_retries2See Retries.
default_headers{}Sent with every request.
http_clienta new httpx.ClientYour 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

python
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

python
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

python
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

python
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 use

The page connects with @dialgem/client. See Web calls.

List calls

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

python
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

python
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

python
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 response

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