Skip to main content

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

Version Python License Tests Typed

📚 Table of Contents

✨ 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_size are 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

  • Client for blocking calls, AsyncClient for await-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-After headers.
  • 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 of ApiError you 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}/Payments lifecycle (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 — /health deep 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.typed marker — 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

🙌 Contributors

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)

Source distribution for voiceml 0.9.2
File Size Uploaded
voiceml-0.9.2.tar.gz 67.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for voiceml 0.9.2
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.9.2 This release

2 release files

0.9.1

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page