📞 VoiceML Python SDK
The official Python client for the VoiceML REST API — Twilio-compatible outbound voice and answering-machine-detection from VoiceTel, with type-safe, async-ready Python.
📚 Table of Contents
- Features
- Installation
- Quickstart
- Authentication
- Resource Reference
- Error Handling
- Async Support
- Pagination
- Migration from twilio-python
- Rate Limits
- Development
- API Documentation
- Contributors
- Sponsors
- License
✨ Features
🛡️ Strongly Typed End-to-End
- Pydantic v2 models for every one of the 81 API operations — request bodies validated before they leave your machine, responses validated when they arrive.
- Autocomplete everywhere. Your IDE knows the shape of every field —
Call.sid,Recording.duration,Queue.current_sizeare all typed. - Twilio-compatible wire shapes —
account_sid,from_number,to_number, status callbacks, pagination envelopes — match what Twilio's Programmable Voice API documents.
⚡ Sync + Async, Same Surface
Clientfor blocking calls,AsyncClientforawait-based async — identical method names, identical return types.- Built on
httpx— HTTP/2 ready, connection pooling, custom transports if you need them. - TLS session cache + persistent connections out of the box.
🔁 Production-Grade Transport
- Automatic retry with exponential backoff on 429 / 5xx — honors
Retry-Afterheaders. - Configurable timeouts per client or per call.
- HTTP Basic auth with
AccountSid:ApiKey— exactly what the Twilio SDK uses, so existing credentials work unchanged. - Structured exception hierarchy —
RateLimitError,AuthenticationError,NotFoundError, etc. all subclasses ofApiErroryou can catch broadly or narrowly.
📞 Complete API Coverage
- Calls — originate, fetch, terminate, update + per-call recordings, streams, siprec, transcriptions, notifications, events, and the
/Calls/{sid}/Paymentslifecycle (Pay TwiML companion). - Conferences — list, fetch, end conferences, plus participants (mute / hold / kick) and conference-scoped recordings.
- Queues — create, list, update, delete, peek, dequeue (front or specific member).
- Applications — CRUD on stored TwiML + callback bundles.
- Recordings — account-wide list, metadata fetch, audio fetch (follows S3 redirect), delete.
- Messages — create, fetch, list (To/From/DateSent filters + pagination), update (Body redaction; Status=canceled), delete.
- IncomingPhoneNumbers — list, fetch, update.
- Notifications — fetch, list.
- SIP — SIP Trunking: Domains (CRUD), CredentialLists + Credentials (CRUD), IpAccessControlLists + IpAddresses (CRUD), Domain↔ACL/CredentialList mappings (historical, Auth/Calls, Auth/Registrations namespaces).
- Routes V2 — Twilio Inbound Processing Region API:
client.routes_v2.sip_domains.fetch(name)/update(name, voice_region=..., friendly_name=...). - Diagnostics —
/healthdeep probe, OpenAPI spec.
🧪 Tested
- 176 unit tests with mocked HTTP layer (
pytest-httpx) and real Pydantic validation on every fixture — spec drift gets caught at parse time. - Integration test suite that runs against a callBroadcast / VoiceML instance — gated by env vars, safe for CI.
📦 Clean Distribution
- Zero codegen footprint — every byte hand-written.
- Built with
hatchling; ships as wheel + sdist. py.typedmarker — downstream type checkers see your imports natively.
🚀 Installation
pip install voiceml
Requires Python 3.10 or later. Tested against 3.10, 3.11, 3.12, and 3.13.
🏁 Quickstart
from voiceml import Client
from voiceml.models import CreateCallRequest
with Client(account_sid="AC…", api_key="…") as c:
call = c.calls.create(
CreateCallRequest(
To="+18005551234",
From="+18005550000",
Url="https://example.com/twiml",
MachineDetection="DetectMessageEnd",
)
)
print(call.sid, call.status)
for q in c.queues.list().queues:
print(q.friendly_name, q.current_size)
🔑 Authentication
Every endpoint uses HTTP Basic with your AccountSid as the username and your per-tenant API key as the password — identical to Twilio's auth shape, so credentials issued for Twilio code work here unchanged.
from voiceml import Client
with Client(account_sid="AC…", api_key="…") as c:
me = c.diagnostics.health() # uses your AccountSid + key on every call
Don't have credentials yet? See voicetel.com/docs/api/v0.9/voiceml/ for issuance and rotation.
🗺️ Resource Reference
| Resource | Sync + Async | Covers |
|---|---|---|
client.calls |
originate, fetch, list, terminate, update | + per-call recordings, streams, siprec, transcriptions, notifications, events, payments |
client.conferences |
list, fetch, end | participants (mute / hold / kick), conference-scoped recordings |
client.queues |
create, list, update, delete | peek, dequeue (front or specific member) |
client.applications |
CRUD on TwiML + callback bundles | |
client.recordings |
account-wide list, metadata, audio fetch, delete | follows S3 redirect for audio |
client.messages |
create, fetch, list, update, delete | To/From/DateSent filters; Body redaction; Status=canceled |
client.incoming_phone_numbers |
list, fetch, update | |
client.notifications |
fetch, list | |
client.conversations_v1 |
conversations, messages, participants, roles, users, credentials, configuration, services | rides conversations.voicetel.com |
client.messaging_v1.services |
create, list, fetch, update, delete | Messaging Service (MG…); rides messaging.voicetel.com |
client.voice_v1 |
byoc_trunks, connection_policies (+ targets), settings, source_ip_mappings, ip_records | |
client.routes_v2 |
phone_numbers, sip_domains | Inbound Processing Region |
client.pricing |
v1.{voice,messaging,phone_numbers}, v2.{voice,trunking} |
read-only rate lookups |
client.diagnostics |
/health, OpenAPI spec |
Every method that takes a request body accepts a typed Pydantic model imported from voiceml.models:
from voiceml import Client
from voiceml.models import CreateCallRequest, StartPaymentRequest
with Client(account_sid="AC…", api_key="…") as c:
call = c.calls.create(CreateCallRequest(
To="+18005551234",
From="+18005550000",
Url="https://example.com/twiml",
))
# On a live call, open a Pay session:
session = c.calls.start_payment(call.sid, StartPaymentRequest(
IdempotencyKey="order-482917",
StatusCallback="https://example.com/pay-status",
))
print(session.sid, session.status)
Product hosts
VoiceML mirrors Twilio's product-per-subdomain model: the Conversations product
answers on conversations.voicetel.com and Messaging Service on
messaging.voicetel.com, while everything else stays on the default
voiceml.voicetel.com. The two share the /v1/Services path shape, so the host
is what disambiguates them. The client derives these automatically from
base_url — you don't set anything for the hosted service.
For a self-hosted deployment on a custom host, point the product base URLs at your own subdomains explicitly (otherwise every product stays on the single host you configured):
Client(
account_sid="AC…",
api_key="…",
base_url="https://pbx.example.com",
messaging_base_url="https://messaging.example.com",
conversations_base_url="https://conversations.example.com",
)
🚨 Error Handling
All HTTP errors raise subclasses of voiceml.ApiError. Catch broadly or narrowly:
| Status | Exception |
|---|---|
| 400 | BadRequestError |
| 401 | AuthenticationError |
| 403 | PermissionDeniedError |
| 404 | NotFoundError |
| 409 | ConflictError |
| 410 | GoneError |
| 429 | RateLimitError |
| 501 | NotImplementedAPIError |
| 5xx | ServerError |
| other | ApiError |
from voiceml import Client, NotFoundError, RateLimitError
with Client(account_sid="AC…", api_key="…") as c:
try:
call = c.calls.get("CA0000000000000000000000000000aaaa")
except NotFoundError:
print("That call isn't on your account.")
except RateLimitError as e:
print(f"Slow down — retry in {e.body.get('retry_after', '?')}s")
The Twilio-compatible error body (code, message, more_info, status) is parsed into error.code / error.message with the raw payload on error.body.
⚡ Async Support
Identical surface to Client, with await-based methods:
import asyncio
from voiceml import AsyncClient
async def main() -> None:
async with AsyncClient(account_sid="AC…", api_key="…") as c:
calls = await c.calls.list(status="in-progress")
for call in calls.calls:
print(call.sid, call.duration)
asyncio.run(main())
📄 Pagination
List operations return a …List model with a Twilio-compatible pagination envelope (page, page_size, total, next_page_uri, previous_page_uri, …). For /Calls and /Messages, use the iter() helper to walk all pages transparently:
for call in c.calls.iter(status="completed", page_size=200):
process(call)
for msg in c.messages.iter(from_number="+18005550000", page_size=200):
archive(msg)
For other resources, page manually with client.<resource>.list(page=n).
🔁 Migration from twilio-python
The account_sid + api_key pair Twilio's SDK validates in its constructor works unchanged here:
# Before — Twilio
from twilio.rest import Client as TwilioClient
client = TwilioClient("AC…", "<token>", region=None)
# After — VoiceML (Twilio-compatible)
from voiceml import Client
client = Client(account_sid="AC…", api_key="<api-key>")
Method names follow the resource map above (client.calls.create(...), client.queues.list(), …) rather than Twilio's client.api.v2010.accounts(sid).calls.create(...) chain — flatter, fewer keystrokes, same wire format on the way out.
⏱️ Rate Limits
VoiceML applies per-tenant rate limits at the edge. The SDK automatically retries 429 responses with Retry-After honored, up to max_retries (default 2). To bump it:
Client(account_sid="AC…", api_key="…", max_retries=4, timeout=60.0)
🛠️ Development
git clone https://github.com/voicetel/voiceml-python-sdk
cd voiceml-python-sdk
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
# Unit tests (fast, no network)
pytest tests/unit
# Lint + type-check
ruff check src tests
mypy src
# Integration tests (live, read-only against a configured VoiceML instance)
cp .env.example .env # fill in VOICEML_ACCOUNT_SID / VOICEML_API_KEY / VOICEML_BASE_URL
pytest tests/integration
# Build wheel + sdist
python -m build
twine check dist/*
📖 API Documentation
- Reference docs: voicetel.com/docs/api/v0.9/voiceml/
- Validator: voicetel.com/voiceml/validator/
- SDK catalogue: voicetel.com/docs/voiceml-sdks/
- Type definitions: see the
voiceml.modelsmodule — every wire shape has a Pydantic model.
🙌 Contributors
- Michael Mavroudis — Lead Developer
Contributions welcome. Open an issue describing the change you want to make, or send a pull request against main.
💖 Sponsors
| Sponsor | Contribution |
|---|---|
| VoiceTel Communications | Primary development and production hosting |
📄 License
MIT with the Commons Clause restriction. See LICENSE and voicetel.com/legal/.
Metadata
Release files for voiceml 0.9.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| voiceml-0.9.2.tar.gz | 67.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| voiceml-0.9.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 156.9 kB
Release files / voiceml-0.9.2.tar.gz
| Download URL | voiceml-0.9.2.tar.gz |
|---|---|
| Size | 67.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8c39d8bb252833e730597ca924cd6413816dfc11ec0f0933ab9f1b8bbd39359a
|
|
BLAKE2b-256 checksum How to use checksums |
e59813543b3c532a2f4e617c8773a14b1045940a6ea2955e1d4549249a46afc6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 9, 2026.
Transparency logRelease files / voiceml-0.9.2-py3-none-any.whl
| Download URL | voiceml-0.9.2-py3-none-any.whl |
|---|---|
| Size | 89.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
139303a6ee625a26ef0db6d2df1665df63a42fc88b0fb0a2892262656e5e0006
|
|
BLAKE2b-256 checksum How to use checksums |
cb5bc526d3a7bb8b93b1ed03ab2d68d65c87603dc2776e3db34d49fb84e41049
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 9, 2026.
Transparency log