Official Python SDK for the VoiceML REST API (Twilio-compatible voice + AMD service from VoiceTel)
Project description
📞 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.
- Diagnostics —
/healthdeep probe, OpenAPI spec.
🧪 Tested
- 57 unit tests with mocked HTTP layer (
respx) 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.6/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.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)
🚨 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.6/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/.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file voiceml-0.7.1.tar.gz.
File metadata
- Download URL: voiceml-0.7.1.tar.gz
- Upload date:
- Size: 31.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63d09040669e708d284965b2873208360bf8c0f010d1596738b21e01430298a2
|
|
| MD5 |
73b82dd35f045396ee91e827cf2acba8
|
|
| BLAKE2b-256 |
9c0add632caae10f121c5c2748e8b3b21ce06f3d1347efad280a3d8c692fe13f
|
Provenance
The following attestation bundles were made for voiceml-0.7.1.tar.gz:
Publisher:
publish.yml on voicetel/voiceml-python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
voiceml-0.7.1.tar.gz -
Subject digest:
63d09040669e708d284965b2873208360bf8c0f010d1596738b21e01430298a2 - Sigstore transparency entry: 1804180723
- Sigstore integration time:
-
Permalink:
voicetel/voiceml-python-sdk@3c2233fedd416e80c06dc22e9b68720335b6a8c9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/voicetel
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3c2233fedd416e80c06dc22e9b68720335b6a8c9 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file voiceml-0.7.1-py3-none-any.whl.
File metadata
- Download URL: voiceml-0.7.1-py3-none-any.whl
- Upload date:
- Size: 45.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae65ece1d286b1d5d8c6487d755cedc1f4dfd1c1fde3fe4331b16529e9d4fe5e
|
|
| MD5 |
8599d273606eaabe2e74015bd3cd0843
|
|
| BLAKE2b-256 |
010a8b19769badc789d31ca2698de54aba6bd06d7dd9e97a5d5eccda055dac2e
|
Provenance
The following attestation bundles were made for voiceml-0.7.1-py3-none-any.whl:
Publisher:
publish.yml on voicetel/voiceml-python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
voiceml-0.7.1-py3-none-any.whl -
Subject digest:
ae65ece1d286b1d5d8c6487d755cedc1f4dfd1c1fde3fe4331b16529e9d4fe5e - Sigstore transparency entry: 1804180782
- Sigstore integration time:
-
Permalink:
voicetel/voiceml-python-sdk@3c2233fedd416e80c06dc22e9b68720335b6a8c9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/voicetel
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3c2233fedd416e80c06dc22e9b68720335b6a8c9 -
Trigger Event:
workflow_dispatch
-
Statement type: