Skip to main content

📞 VoiceTel Python SDK

The official Python client for the VoiceTel REST API — provision numbers, place orders, validate e911, send messages, and manage your account, all with type-safe, async-ready Python.

Version Python License Coverage Typed

📚 Table of Contents

✨ Features

🛡️ Strongly Typed End-to-End

  • Pydantic v2 models for every one of the 73 API operations — request bodies validated before they leave your machine, responses validated when they arrive.
  • mypy-strict clean. Full type coverage, including async, generics, and discriminated unions.
  • Autocomplete everywhere. Your IDE knows the shape of every field — no more guessing what's in result["data"]["numbers"].

⚡ Sync + Async, Same Surface

  • Client for blocking calls, AsyncClient for await-based async — identical method names, identical return types.
  • Built on httpx — supports HTTP/2, connection pooling, and custom transports if you need them.

🔁 Production-Grade Transport

  • Automatic retry with exponential backoff on 429 / 5xx — honors Retry-After headers.
  • Configurable timeouts per client or per call.
  • Bearer auth managed for you; password→key exchange handled by client.login().
  • Structured exception hierarchy — RateLimitError, AuthenticationError, NotFoundError, etc. all subclasses of ApiError you can catch broadly or narrowly.

📞 Complete API Coverage

  • Numbers — list, get, add, remove, route, translate, CNAM, LIDB, fax, forward, SMS, messaging campaigns, port-out PIN, account moves.
  • Account — profile, sub-accounts, CDRs, credits, payments, MRC, registration, password recovery.
  • e911 — record provisioning, address validation, lookup, removal.
  • Gateways — list, create, update, delete, view bound numbers.
  • Messaging — SMS & MMS sending, message history, 10DLC brand and campaign registration, per-number messaging state.
  • Lookups — CNAM and LRN dips.
  • iNumbering — inventory search, coverage queries, number orders, port-in submissions, port-out availability checks.
  • Support — ticket create / read / update / delete, threaded messages, replies.
  • ACL — IP allowlist management with structured 409 conflict bodies.
  • Authentication — switch between Digest, IP, Lax (Digest | IP), or Strict (Digest + IP) modes; rotate passwords.

🧪 Battle-Tested

  • 108 unit tests at 100% statement + branch coverage.
  • Integration test suite that runs read-only operations against api.voicetel.com — gated by env vars, safe for CI.
  • No mocks-pretending-to-be-tests. Mocked HTTP layer with respx, real Pydantic validation on every fixture so spec drift gets caught.

📦 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 voicetel-api

Requires Python 3.10 or later. Tested against 3.10, 3.11, 3.12, and 3.13.

🏁 Quickstart

from voicetel import Client

with Client() as c:
    # Exchange username + password for an API key (one-time per session)
    c.login(username=1000000001, password="hunter2")

    # Typed responses — your IDE knows what `me` is.
    me = c.account.get()
    print(f"Balance: ${me.cash:.2f}  |  Caller ID: {me.callerId}")

    # List your numbers
    for n in c.numbers.list().numbers:
        print(f"{n.number}  route={n.route}  cnam={n.cnam}  sms={n.smsEnabled}")

Or, if you already have an API key:

from voicetel import Client

with Client(api_key="32hex...") as c:
    coverage = c.inumbering.coverage(state="NJ")
    for bucket in coverage.coverage:
        print(f"{bucket.npa}-{bucket.nxx}: {bucket.count} TNs available")

🔑 Authentication

Every endpoint requires Authorization: Bearer <apikey> except POST /v2.2/account/api-key, which exchanges username + password for a fresh key. Client.login() (and AsyncClient.login()) handles the exchange and installs the returned key on the transport.

Re-fetch the API key after any password change — the old one is invalidated.

Don't have credentials yet? Get them at voicetel.com/docs/api/v2.2/credentials.

from voicetel import Client

with Client() as c:
    key = c.login(username=1000000001, password="hunter2")
    # `key` is the new 32-hex bearer; the client already has it installed.

🗺️ Resource Reference

Resource Operations Example
client.account Profile, CDR, credits, payments, MRC, signup, recovery, sub-accounts c.account.cdr(start=t1, end=t2)
client.acl IP allowlist (CIDR entries) c.acl.add(AclModifyRequest(acl=[...]))
client.authentication SIP/HTTP auth mode + password c.authentication.update(AuthPutRequest(authType=1))
client.e911 Records, address validation, provisioning c.e911.validate(E911AddressRequest(...))
client.gateways Termination routes c.gateways.list()
client.inumbering Inventory, orders, port-ins c.inumbering.search_inventory(npa=201)
client.lookups CNAM & LRN dips c.lookups.lrn("2015551234", ani="2012548000")
client.messaging SMS/MMS, 10DLC brands & campaigns c.messaging.send(MessageSendRequest(...))
client.numbers All operations on TNs on the account c.numbers.assign_campaign("2015551234", ...)
client.support Tickets, replies, attachments c.support.create(TicketCreateRequest(...))

Every method that takes a request body accepts a typed Pydantic model imported from voicetel.models:

from voicetel import Client
from voicetel.models import (
    MessageSendRequest,
    NumberCampaignAssignRequest,
    PortSubmitRequest,
)

with Client(api_key=key) as c:
    sent = c.messaging.send(MessageSendRequest(
        fromNumber="2012548000",
        toNumber="2015551234",
        text="Your code is 482917",
    ))
    print(f"Sent: {sent.id}  ({sent.parts} segment(s))")

    c.numbers.assign_campaign(
        "2015551234",
        NumberCampaignAssignRequest(campaignId="CABC123"),
    )

🚨 Error Handling

All HTTP errors raise subclasses of voicetel.ApiError. Catch broadly or narrowly:

Status Exception
400 BadRequestError
401 AuthenticationError
403 PermissionDeniedError
404 NotFoundError
409 ConflictError
429 RateLimitError
5xx ServerError
other ApiError
from voicetel import Client, NotFoundError, RateLimitError

with Client(api_key=key) as c:
    try:
        n = c.numbers.get("9999999999")
    except NotFoundError:
        print("That number isn't on your account.")
    except RateLimitError as e:
        print(f"Slow down — retry in {e.body.get('retryAfter', '?')}s")

⚡ Async Support

Identical surface to Client, with await-based methods:

import asyncio
from voicetel import AsyncClient

async def fetch_state(numbers: list[str]) -> None:
    async with AsyncClient(api_key="...") as c:
        state = await c.messaging.numbers_state(numbers=numbers)
        for s in state.numbers:
            print(f"{s.number}: network={s.network} campaign={s.campaign}")

asyncio.run(fetch_state(["2015551234", "2015551235"]))

⏱️ Rate Limits

These endpoints are limited to 6 requests per hour per IP:

  • account/info
  • account/mrc (client.account.recurring_charges())
  • account/cdr (client.account.cdr())
  • account/api-key (client.login())

The SDK automatically retries 429 responses with Retry-After honored, up to max_retries (default 2). To bump it:

Client(api_key=key, max_retries=4, timeout=60.0)

🛠️ Development

git clone https://github.com/voicetel/python-sdk
cd python-sdk
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

# Unit tests (fast, no network)
pytest tests/unit

# 100% coverage gate
pytest tests/unit --cov --cov-fail-under=100

# Lint + type-check
ruff check src tests
mypy src

# Integration tests (live api.voicetel.com, read-only)
cp .env.example .env  # fill in VOICETEL_USERNAME / VOICETEL_PASSWORD
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

This project is licensed under the MIT License — see the LICENSE file for details.

Metadata

Release files for voicetel-api 2.2.14

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for voicetel-api 2.2.14
File Size Uploaded
voicetel_api-2.2.14.tar.gz 31.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for voicetel-api 2.2.14
File Interpreter ABI Platform
voicetel_api-2.2.14-py3-none-any.whl Python 3 none any Details

Total release size: 75.2 kB

Release files / voicetel_api-2.2.14.tar.gz

Download URL voicetel_api-2.2.14.tar.gz
Size 31.2 kB
Tags Source
SHA-256 checksum
How to use checksums
5269601a72d0c99b2a4ebf92a83600c2bc7bf60823dfcc84b47368d2c86a86ca
BLAKE2b-256 checksum
How to use checksums
2306f687c5038cea3eb652b8ef03951861f16e3edcdc16e97ab5b6eac04d070b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 14, 2026.

Transparency log

Release files / voicetel_api-2.2.14-py3-none-any.whl

Download URL voicetel_api-2.2.14-py3-none-any.whl
Size 43.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8953938ef698a8a4d13f4ac85c93a92ad1248d8c3a6f91533585a68be76e6730
BLAKE2b-256 checksum
How to use checksums
8c8414178c9c4be90e0d8ed81b5630d0655e525d743735ba256cef07fd2f426e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 14, 2026.

Transparency log

Release history Release notifications | RSS feed

2.2.17

2 release files

2.2.16

2 release files

2.2.15

2 release files

This release

2.2.14 This release

2 release files

2.2.10

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