voiceml 0.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. voiceml-0.7.1/.gitignore +25 -0
  2. voiceml-0.7.1/LICENSE +23 -0
  3. voiceml-0.7.1/PKG-INFO +307 -0
  4. voiceml-0.7.1/README.md +274 -0
  5. voiceml-0.7.1/pyproject.toml +71 -0
  6. voiceml-0.7.1/src/voiceml/__init__.py +62 -0
  7. voiceml-0.7.1/src/voiceml/_http.py +316 -0
  8. voiceml-0.7.1/src/voiceml/_version.py +1 -0
  9. voiceml-0.7.1/src/voiceml/client.py +176 -0
  10. voiceml-0.7.1/src/voiceml/exceptions.py +121 -0
  11. voiceml-0.7.1/src/voiceml/models/__init__.py +195 -0
  12. voiceml-0.7.1/src/voiceml/models/_base.py +54 -0
  13. voiceml-0.7.1/src/voiceml/models/applications.py +56 -0
  14. voiceml-0.7.1/src/voiceml/models/calls.py +151 -0
  15. voiceml-0.7.1/src/voiceml/models/common.py +58 -0
  16. voiceml-0.7.1/src/voiceml/models/conferences.py +97 -0
  17. voiceml-0.7.1/src/voiceml/models/diagnostics.py +45 -0
  18. voiceml-0.7.1/src/voiceml/models/incoming_phone_numbers.py +120 -0
  19. voiceml-0.7.1/src/voiceml/models/messages.py +104 -0
  20. voiceml-0.7.1/src/voiceml/models/payments.py +97 -0
  21. voiceml-0.7.1/src/voiceml/models/queues.py +61 -0
  22. voiceml-0.7.1/src/voiceml/models/recordings.py +103 -0
  23. voiceml-0.7.1/src/voiceml/models/siprec.py +50 -0
  24. voiceml-0.7.1/src/voiceml/models/streams.py +43 -0
  25. voiceml-0.7.1/src/voiceml/models/transcriptions.py +55 -0
  26. voiceml-0.7.1/src/voiceml/py.typed +0 -0
  27. voiceml-0.7.1/src/voiceml/resources/__init__.py +37 -0
  28. voiceml-0.7.1/src/voiceml/resources/_base.py +66 -0
  29. voiceml-0.7.1/src/voiceml/resources/applications.py +105 -0
  30. voiceml-0.7.1/src/voiceml/resources/calls.py +920 -0
  31. voiceml-0.7.1/src/voiceml/resources/conferences.py +418 -0
  32. voiceml-0.7.1/src/voiceml/resources/diagnostics.py +54 -0
  33. voiceml-0.7.1/src/voiceml/resources/incoming_phone_numbers.py +588 -0
  34. voiceml-0.7.1/src/voiceml/resources/messages.py +277 -0
  35. voiceml-0.7.1/src/voiceml/resources/notifications.py +101 -0
  36. voiceml-0.7.1/src/voiceml/resources/queues.py +208 -0
  37. voiceml-0.7.1/src/voiceml/resources/recordings.py +196 -0
@@ -0,0 +1,25 @@
1
+ # Build / packaging
2
+ build/
3
+ dist/
4
+ *.egg-info/
5
+ *.egg
6
+
7
+ # Bytecode + caches
8
+ __pycache__/
9
+ *.pyc
10
+ *.pyo
11
+ .mypy_cache/
12
+ .pytest_cache/
13
+ .ruff_cache/
14
+ .coverage
15
+ htmlcov/
16
+
17
+ # Virtualenvs
18
+ .venv/
19
+ venv/
20
+ env/
21
+
22
+ # OS / editor
23
+ .DS_Store
24
+ .vscode/
25
+ .idea/
voiceml-0.7.1/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ MIT License with Commons Clause Restriction
2
+
3
+ Copyright (c) 2026 VoiceTel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ See https://voicetel.com/legal/ for the Commons Clause restriction.
voiceml-0.7.1/PKG-INFO ADDED
@@ -0,0 +1,307 @@
1
+ Metadata-Version: 2.4
2
+ Name: voiceml
3
+ Version: 0.7.1
4
+ Summary: Official Python SDK for the VoiceML REST API (Twilio-compatible voice + AMD service from VoiceTel)
5
+ Project-URL: Homepage, https://voiceml.voicetel.com
6
+ Project-URL: Repository, https://github.com/voicetel/voiceml-python-sdk
7
+ Project-URL: Issues, https://github.com/voicetel/voiceml-python-sdk/issues
8
+ Author-email: VoiceTel <support@voicetel.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: amd,telephony,twilio-compatible,voice,voiceml,voicetel
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Communications :: Telephony
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx>=0.27
25
+ Requires-Dist: pydantic>=2.6
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy>=1.10; extra == 'dev'
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
29
+ Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
30
+ Requires-Dist: pytest>=8.0; extra == 'dev'
31
+ Requires-Dist: ruff>=0.5; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # πŸ“ž VoiceML Python SDK
35
+
36
+ The official Python client for the [VoiceML REST API](https://voicetel.com/docs/api/v0.6/voiceml/) β€” Twilio-compatible outbound voice and answering-machine-detection from VoiceTel, with type-safe, async-ready Python.
37
+
38
+ ![Version](https://img.shields.io/badge/version-0.7.1-blue)
39
+ ![Python](https://img.shields.io/badge/python-3.10%2B-blue)
40
+ ![License](https://img.shields.io/badge/license-MIT%20%2B%20Commons%20Clause-green)
41
+ ![Tests](https://img.shields.io/badge/tests-57%20unit-brightgreen)
42
+ ![Typed](https://img.shields.io/badge/typed-pydantic--v2-blue)
43
+
44
+ ## πŸ“š Table of Contents
45
+
46
+ - [Features](#-features)
47
+ - [Installation](#-installation)
48
+ - [Quickstart](#-quickstart)
49
+ - [Authentication](#-authentication)
50
+ - [Resource Reference](#-resource-reference)
51
+ - [Error Handling](#-error-handling)
52
+ - [Async Support](#-async-support)
53
+ - [Pagination](#-pagination)
54
+ - [Migration from twilio-python](#-migration-from-twilio-python)
55
+ - [Rate Limits](#-rate-limits)
56
+ - [Development](#-development)
57
+ - [API Documentation](#-api-documentation)
58
+ - [Contributors](#-contributors)
59
+ - [Sponsors](#-sponsors)
60
+ - [License](#-license)
61
+
62
+ ## ✨ Features
63
+
64
+ ### πŸ›‘οΈ Strongly Typed End-to-End
65
+ - **Pydantic v2 models** for every one of the 81 API operations β€” request bodies validated before they leave your machine, responses validated when they arrive.
66
+ - **Autocomplete everywhere.** Your IDE knows the shape of every field β€” `Call.sid`, `Recording.duration`, `Queue.current_size` are all typed.
67
+ - **Twilio-compatible wire shapes** β€” `account_sid`, `from_number`, `to_number`, status callbacks, pagination envelopes β€” match what Twilio's Programmable Voice API documents.
68
+
69
+ ### ⚑ Sync + Async, Same Surface
70
+ - `Client` for blocking calls, `AsyncClient` for `await`-based async β€” identical method names, identical return types.
71
+ - Built on `httpx` β€” HTTP/2 ready, connection pooling, custom transports if you need them.
72
+ - TLS session cache + persistent connections out of the box.
73
+
74
+ ### πŸ” Production-Grade Transport
75
+ - **Automatic retry** with exponential backoff on 429 / 5xx β€” honors `Retry-After` headers.
76
+ - **Configurable timeouts** per client or per call.
77
+ - **HTTP Basic auth** with `AccountSid:ApiKey` β€” exactly what the Twilio SDK uses, so existing credentials work unchanged.
78
+ - **Structured exception hierarchy** β€” `RateLimitError`, `AuthenticationError`, `NotFoundError`, etc. all subclasses of `ApiError` you can catch broadly or narrowly.
79
+
80
+ ### πŸ“ž Complete API Coverage
81
+ - **Calls** β€” originate, fetch, terminate, update + per-call recordings, streams, siprec, transcriptions, notifications, events, and the `/Calls/{sid}/Payments` lifecycle (Pay TwiML companion).
82
+ - **Conferences** β€” list, fetch, end conferences, plus participants (mute / hold / kick) and conference-scoped recordings.
83
+ - **Queues** β€” create, list, update, delete, peek, dequeue (front or specific member).
84
+ - **Applications** β€” CRUD on stored TwiML + callback bundles.
85
+ - **Recordings** β€” account-wide list, metadata fetch, audio fetch (follows S3 redirect), delete.
86
+ - **Messages** β€” create, fetch, list (To/From/DateSent filters + pagination), update (Body redaction; Status=canceled), delete.
87
+ - **IncomingPhoneNumbers** β€” list, fetch, update.
88
+ - **Notifications** β€” fetch, list.
89
+ - **Diagnostics** β€” `/health` deep probe, OpenAPI spec.
90
+
91
+ ### πŸ§ͺ Tested
92
+ - **57 unit tests** with mocked HTTP layer (`respx`) and real Pydantic validation on every fixture β€” spec drift gets caught at parse time.
93
+ - **Integration test suite** that runs against a callBroadcast / VoiceML instance β€” gated by env vars, safe for CI.
94
+
95
+ ### πŸ“¦ Clean Distribution
96
+ - Zero codegen footprint β€” every byte hand-written.
97
+ - Built with `hatchling`; ships as wheel + sdist.
98
+ - `py.typed` marker β€” downstream type checkers see your imports natively.
99
+
100
+ ## πŸš€ Installation
101
+
102
+ ```bash
103
+ pip install voiceml
104
+ ```
105
+
106
+ Requires Python 3.10 or later. Tested against 3.10, 3.11, 3.12, and 3.13.
107
+
108
+ ## 🏁 Quickstart
109
+
110
+ ```python
111
+ from voiceml import Client
112
+ from voiceml.models import CreateCallRequest
113
+
114
+ with Client(account_sid="AC…", api_key="…") as c:
115
+ call = c.calls.create(
116
+ CreateCallRequest(
117
+ To="+18005551234",
118
+ From="+18005550000",
119
+ Url="https://example.com/twiml",
120
+ MachineDetection="DetectMessageEnd",
121
+ )
122
+ )
123
+ print(call.sid, call.status)
124
+
125
+ for q in c.queues.list().queues:
126
+ print(q.friendly_name, q.current_size)
127
+ ```
128
+
129
+ ## πŸ”‘ Authentication
130
+
131
+ 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.
132
+
133
+ ```python
134
+ from voiceml import Client
135
+
136
+ with Client(account_sid="AC…", api_key="…") as c:
137
+ me = c.diagnostics.health() # uses your AccountSid + key on every call
138
+ ```
139
+
140
+ > Don't have credentials yet? See **[voicetel.com/docs/api/v0.6/voiceml/](https://voicetel.com/docs/api/v0.6/voiceml/)** for issuance and rotation.
141
+
142
+ ## πŸ—ΊοΈ Resource Reference
143
+
144
+ | Resource | Sync + Async | Covers |
145
+ |---|---|---|
146
+ | `client.calls` | originate, fetch, list, terminate, update | + per-call recordings, streams, siprec, transcriptions, notifications, events, payments |
147
+ | `client.conferences` | list, fetch, end | participants (mute / hold / kick), conference-scoped recordings |
148
+ | `client.queues` | create, list, update, delete | peek, dequeue (front or specific member) |
149
+ | `client.applications` | CRUD on TwiML + callback bundles | |
150
+ | `client.recordings` | account-wide list, metadata, audio fetch, delete | follows S3 redirect for audio |
151
+ | `client.messages` | create, fetch, list, update, delete | To/From/DateSent filters; Body redaction; Status=canceled |
152
+ | `client.incoming_phone_numbers` | list, fetch, update | |
153
+ | `client.notifications` | fetch, list | |
154
+ | `client.diagnostics` | `/health`, OpenAPI spec | |
155
+
156
+ Every method that takes a request body accepts a typed Pydantic model imported from `voiceml.models`:
157
+
158
+ ```python
159
+ from voiceml import Client
160
+ from voiceml.models import CreateCallRequest, StartPaymentRequest
161
+
162
+ with Client(account_sid="AC…", api_key="…") as c:
163
+ call = c.calls.create(CreateCallRequest(
164
+ To="+18005551234",
165
+ From="+18005550000",
166
+ Url="https://example.com/twiml",
167
+ ))
168
+ # On a live call, open a Pay session:
169
+ session = c.calls.start_payment(call.sid, StartPaymentRequest(
170
+ IdempotencyKey="order-482917",
171
+ StatusCallback="https://example.com/pay-status",
172
+ ))
173
+ print(session.sid, session.status)
174
+ ```
175
+
176
+ ## 🚨 Error Handling
177
+
178
+ All HTTP errors raise subclasses of `voiceml.ApiError`. Catch broadly or narrowly:
179
+
180
+ | Status | Exception |
181
+ |--------|-----------|
182
+ | 400 | `BadRequestError` |
183
+ | 401 | `AuthenticationError` |
184
+ | 403 | `PermissionDeniedError` |
185
+ | 404 | `NotFoundError` |
186
+ | 409 | `ConflictError` |
187
+ | 410 | `GoneError` |
188
+ | 429 | `RateLimitError` |
189
+ | 501 | `NotImplementedAPIError` |
190
+ | 5xx | `ServerError` |
191
+ | other | `ApiError` |
192
+
193
+ ```python
194
+ from voiceml import Client, NotFoundError, RateLimitError
195
+
196
+ with Client(account_sid="AC…", api_key="…") as c:
197
+ try:
198
+ call = c.calls.get("CA0000000000000000000000000000aaaa")
199
+ except NotFoundError:
200
+ print("That call isn't on your account.")
201
+ except RateLimitError as e:
202
+ print(f"Slow down β€” retry in {e.body.get('retry_after', '?')}s")
203
+ ```
204
+
205
+ The Twilio-compatible error body (`code`, `message`, `more_info`, `status`) is parsed into `error.code` / `error.message` with the raw payload on `error.body`.
206
+
207
+ ## ⚑ Async Support
208
+
209
+ Identical surface to `Client`, with `await`-based methods:
210
+
211
+ ```python
212
+ import asyncio
213
+ from voiceml import AsyncClient
214
+
215
+ async def main() -> None:
216
+ async with AsyncClient(account_sid="AC…", api_key="…") as c:
217
+ calls = await c.calls.list(status="in-progress")
218
+ for call in calls.calls:
219
+ print(call.sid, call.duration)
220
+
221
+ asyncio.run(main())
222
+ ```
223
+
224
+ ## πŸ“„ Pagination
225
+
226
+ 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:
227
+
228
+ ```python
229
+ for call in c.calls.iter(status="completed", page_size=200):
230
+ process(call)
231
+
232
+ for msg in c.messages.iter(from_number="+18005550000", page_size=200):
233
+ archive(msg)
234
+ ```
235
+
236
+ For other resources, page manually with `client.<resource>.list(page=n)`.
237
+
238
+ ## πŸ” Migration from twilio-python
239
+
240
+ The `account_sid` + `api_key` pair Twilio's SDK validates in its constructor works unchanged here:
241
+
242
+ ```python
243
+ # Before β€” Twilio
244
+ from twilio.rest import Client as TwilioClient
245
+ client = TwilioClient("AC…", "<token>", region=None)
246
+
247
+ # After β€” VoiceML (Twilio-compatible)
248
+ from voiceml import Client
249
+ client = Client(account_sid="AC…", api_key="<api-key>")
250
+ ```
251
+
252
+ 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.
253
+
254
+ ## ⏱️ Rate Limits
255
+
256
+ 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:
257
+
258
+ ```python
259
+ Client(account_sid="AC…", api_key="…", max_retries=4, timeout=60.0)
260
+ ```
261
+
262
+ ## πŸ› οΈ Development
263
+
264
+ ```bash
265
+ git clone https://github.com/voicetel/voiceml-python-sdk
266
+ cd voiceml-python-sdk
267
+ python -m venv .venv && source .venv/bin/activate
268
+ pip install -e ".[dev]"
269
+
270
+ # Unit tests (fast, no network)
271
+ pytest tests/unit
272
+
273
+ # Lint + type-check
274
+ ruff check src tests
275
+ mypy src
276
+
277
+ # Integration tests (live, read-only against a configured VoiceML instance)
278
+ cp .env.example .env # fill in VOICEML_ACCOUNT_SID / VOICEML_API_KEY / VOICEML_BASE_URL
279
+ pytest tests/integration
280
+
281
+ # Build wheel + sdist
282
+ python -m build
283
+ twine check dist/*
284
+ ```
285
+
286
+ ## πŸ“– API Documentation
287
+
288
+ - **Reference docs:** [voicetel.com/docs/api/v0.6/voiceml/](https://voicetel.com/docs/api/v0.6/voiceml/)
289
+ - **Validator:** [voicetel.com/voiceml/validator/](https://voicetel.com/voiceml/validator/)
290
+ - **SDK catalogue:** [voicetel.com/docs/voiceml-sdks/](https://voicetel.com/docs/voiceml-sdks/)
291
+ - **Type definitions:** see the `voiceml.models` module β€” every wire shape has a Pydantic model.
292
+
293
+ ## πŸ™Œ Contributors
294
+
295
+ - [Michael Mavroudis](https://github.com/mavroudis) β€” Lead Developer
296
+
297
+ Contributions welcome. Open an issue describing the change you want to make, or send a pull request against `main`.
298
+
299
+ ## πŸ’– Sponsors
300
+
301
+ | Sponsor | Contribution |
302
+ |---------|--------------|
303
+ | [VoiceTel Communications](https://voicetel.com) | Primary development and production hosting |
304
+
305
+ ## πŸ“„ License
306
+
307
+ MIT with the Commons Clause restriction. See [LICENSE](LICENSE) and [voicetel.com/legal/](https://voicetel.com/legal/).
@@ -0,0 +1,274 @@
1
+ # πŸ“ž VoiceML Python SDK
2
+
3
+ The official Python client for the [VoiceML REST API](https://voicetel.com/docs/api/v0.6/voiceml/) β€” Twilio-compatible outbound voice and answering-machine-detection from VoiceTel, with type-safe, async-ready Python.
4
+
5
+ ![Version](https://img.shields.io/badge/version-0.7.1-blue)
6
+ ![Python](https://img.shields.io/badge/python-3.10%2B-blue)
7
+ ![License](https://img.shields.io/badge/license-MIT%20%2B%20Commons%20Clause-green)
8
+ ![Tests](https://img.shields.io/badge/tests-57%20unit-brightgreen)
9
+ ![Typed](https://img.shields.io/badge/typed-pydantic--v2-blue)
10
+
11
+ ## πŸ“š Table of Contents
12
+
13
+ - [Features](#-features)
14
+ - [Installation](#-installation)
15
+ - [Quickstart](#-quickstart)
16
+ - [Authentication](#-authentication)
17
+ - [Resource Reference](#-resource-reference)
18
+ - [Error Handling](#-error-handling)
19
+ - [Async Support](#-async-support)
20
+ - [Pagination](#-pagination)
21
+ - [Migration from twilio-python](#-migration-from-twilio-python)
22
+ - [Rate Limits](#-rate-limits)
23
+ - [Development](#-development)
24
+ - [API Documentation](#-api-documentation)
25
+ - [Contributors](#-contributors)
26
+ - [Sponsors](#-sponsors)
27
+ - [License](#-license)
28
+
29
+ ## ✨ Features
30
+
31
+ ### πŸ›‘οΈ Strongly Typed End-to-End
32
+ - **Pydantic v2 models** for every one of the 81 API operations β€” request bodies validated before they leave your machine, responses validated when they arrive.
33
+ - **Autocomplete everywhere.** Your IDE knows the shape of every field β€” `Call.sid`, `Recording.duration`, `Queue.current_size` are all typed.
34
+ - **Twilio-compatible wire shapes** β€” `account_sid`, `from_number`, `to_number`, status callbacks, pagination envelopes β€” match what Twilio's Programmable Voice API documents.
35
+
36
+ ### ⚑ Sync + Async, Same Surface
37
+ - `Client` for blocking calls, `AsyncClient` for `await`-based async β€” identical method names, identical return types.
38
+ - Built on `httpx` β€” HTTP/2 ready, connection pooling, custom transports if you need them.
39
+ - TLS session cache + persistent connections out of the box.
40
+
41
+ ### πŸ” Production-Grade Transport
42
+ - **Automatic retry** with exponential backoff on 429 / 5xx β€” honors `Retry-After` headers.
43
+ - **Configurable timeouts** per client or per call.
44
+ - **HTTP Basic auth** with `AccountSid:ApiKey` β€” exactly what the Twilio SDK uses, so existing credentials work unchanged.
45
+ - **Structured exception hierarchy** β€” `RateLimitError`, `AuthenticationError`, `NotFoundError`, etc. all subclasses of `ApiError` you can catch broadly or narrowly.
46
+
47
+ ### πŸ“ž Complete API Coverage
48
+ - **Calls** β€” originate, fetch, terminate, update + per-call recordings, streams, siprec, transcriptions, notifications, events, and the `/Calls/{sid}/Payments` lifecycle (Pay TwiML companion).
49
+ - **Conferences** β€” list, fetch, end conferences, plus participants (mute / hold / kick) and conference-scoped recordings.
50
+ - **Queues** β€” create, list, update, delete, peek, dequeue (front or specific member).
51
+ - **Applications** β€” CRUD on stored TwiML + callback bundles.
52
+ - **Recordings** β€” account-wide list, metadata fetch, audio fetch (follows S3 redirect), delete.
53
+ - **Messages** β€” create, fetch, list (To/From/DateSent filters + pagination), update (Body redaction; Status=canceled), delete.
54
+ - **IncomingPhoneNumbers** β€” list, fetch, update.
55
+ - **Notifications** β€” fetch, list.
56
+ - **Diagnostics** β€” `/health` deep probe, OpenAPI spec.
57
+
58
+ ### πŸ§ͺ Tested
59
+ - **57 unit tests** with mocked HTTP layer (`respx`) and real Pydantic validation on every fixture β€” spec drift gets caught at parse time.
60
+ - **Integration test suite** that runs against a callBroadcast / VoiceML instance β€” gated by env vars, safe for CI.
61
+
62
+ ### πŸ“¦ Clean Distribution
63
+ - Zero codegen footprint β€” every byte hand-written.
64
+ - Built with `hatchling`; ships as wheel + sdist.
65
+ - `py.typed` marker β€” downstream type checkers see your imports natively.
66
+
67
+ ## πŸš€ Installation
68
+
69
+ ```bash
70
+ pip install voiceml
71
+ ```
72
+
73
+ Requires Python 3.10 or later. Tested against 3.10, 3.11, 3.12, and 3.13.
74
+
75
+ ## 🏁 Quickstart
76
+
77
+ ```python
78
+ from voiceml import Client
79
+ from voiceml.models import CreateCallRequest
80
+
81
+ with Client(account_sid="AC…", api_key="…") as c:
82
+ call = c.calls.create(
83
+ CreateCallRequest(
84
+ To="+18005551234",
85
+ From="+18005550000",
86
+ Url="https://example.com/twiml",
87
+ MachineDetection="DetectMessageEnd",
88
+ )
89
+ )
90
+ print(call.sid, call.status)
91
+
92
+ for q in c.queues.list().queues:
93
+ print(q.friendly_name, q.current_size)
94
+ ```
95
+
96
+ ## πŸ”‘ Authentication
97
+
98
+ 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.
99
+
100
+ ```python
101
+ from voiceml import Client
102
+
103
+ with Client(account_sid="AC…", api_key="…") as c:
104
+ me = c.diagnostics.health() # uses your AccountSid + key on every call
105
+ ```
106
+
107
+ > Don't have credentials yet? See **[voicetel.com/docs/api/v0.6/voiceml/](https://voicetel.com/docs/api/v0.6/voiceml/)** for issuance and rotation.
108
+
109
+ ## πŸ—ΊοΈ Resource Reference
110
+
111
+ | Resource | Sync + Async | Covers |
112
+ |---|---|---|
113
+ | `client.calls` | originate, fetch, list, terminate, update | + per-call recordings, streams, siprec, transcriptions, notifications, events, payments |
114
+ | `client.conferences` | list, fetch, end | participants (mute / hold / kick), conference-scoped recordings |
115
+ | `client.queues` | create, list, update, delete | peek, dequeue (front or specific member) |
116
+ | `client.applications` | CRUD on TwiML + callback bundles | |
117
+ | `client.recordings` | account-wide list, metadata, audio fetch, delete | follows S3 redirect for audio |
118
+ | `client.messages` | create, fetch, list, update, delete | To/From/DateSent filters; Body redaction; Status=canceled |
119
+ | `client.incoming_phone_numbers` | list, fetch, update | |
120
+ | `client.notifications` | fetch, list | |
121
+ | `client.diagnostics` | `/health`, OpenAPI spec | |
122
+
123
+ Every method that takes a request body accepts a typed Pydantic model imported from `voiceml.models`:
124
+
125
+ ```python
126
+ from voiceml import Client
127
+ from voiceml.models import CreateCallRequest, StartPaymentRequest
128
+
129
+ with Client(account_sid="AC…", api_key="…") as c:
130
+ call = c.calls.create(CreateCallRequest(
131
+ To="+18005551234",
132
+ From="+18005550000",
133
+ Url="https://example.com/twiml",
134
+ ))
135
+ # On a live call, open a Pay session:
136
+ session = c.calls.start_payment(call.sid, StartPaymentRequest(
137
+ IdempotencyKey="order-482917",
138
+ StatusCallback="https://example.com/pay-status",
139
+ ))
140
+ print(session.sid, session.status)
141
+ ```
142
+
143
+ ## 🚨 Error Handling
144
+
145
+ All HTTP errors raise subclasses of `voiceml.ApiError`. Catch broadly or narrowly:
146
+
147
+ | Status | Exception |
148
+ |--------|-----------|
149
+ | 400 | `BadRequestError` |
150
+ | 401 | `AuthenticationError` |
151
+ | 403 | `PermissionDeniedError` |
152
+ | 404 | `NotFoundError` |
153
+ | 409 | `ConflictError` |
154
+ | 410 | `GoneError` |
155
+ | 429 | `RateLimitError` |
156
+ | 501 | `NotImplementedAPIError` |
157
+ | 5xx | `ServerError` |
158
+ | other | `ApiError` |
159
+
160
+ ```python
161
+ from voiceml import Client, NotFoundError, RateLimitError
162
+
163
+ with Client(account_sid="AC…", api_key="…") as c:
164
+ try:
165
+ call = c.calls.get("CA0000000000000000000000000000aaaa")
166
+ except NotFoundError:
167
+ print("That call isn't on your account.")
168
+ except RateLimitError as e:
169
+ print(f"Slow down β€” retry in {e.body.get('retry_after', '?')}s")
170
+ ```
171
+
172
+ The Twilio-compatible error body (`code`, `message`, `more_info`, `status`) is parsed into `error.code` / `error.message` with the raw payload on `error.body`.
173
+
174
+ ## ⚑ Async Support
175
+
176
+ Identical surface to `Client`, with `await`-based methods:
177
+
178
+ ```python
179
+ import asyncio
180
+ from voiceml import AsyncClient
181
+
182
+ async def main() -> None:
183
+ async with AsyncClient(account_sid="AC…", api_key="…") as c:
184
+ calls = await c.calls.list(status="in-progress")
185
+ for call in calls.calls:
186
+ print(call.sid, call.duration)
187
+
188
+ asyncio.run(main())
189
+ ```
190
+
191
+ ## πŸ“„ Pagination
192
+
193
+ 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:
194
+
195
+ ```python
196
+ for call in c.calls.iter(status="completed", page_size=200):
197
+ process(call)
198
+
199
+ for msg in c.messages.iter(from_number="+18005550000", page_size=200):
200
+ archive(msg)
201
+ ```
202
+
203
+ For other resources, page manually with `client.<resource>.list(page=n)`.
204
+
205
+ ## πŸ” Migration from twilio-python
206
+
207
+ The `account_sid` + `api_key` pair Twilio's SDK validates in its constructor works unchanged here:
208
+
209
+ ```python
210
+ # Before β€” Twilio
211
+ from twilio.rest import Client as TwilioClient
212
+ client = TwilioClient("AC…", "<token>", region=None)
213
+
214
+ # After β€” VoiceML (Twilio-compatible)
215
+ from voiceml import Client
216
+ client = Client(account_sid="AC…", api_key="<api-key>")
217
+ ```
218
+
219
+ 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.
220
+
221
+ ## ⏱️ Rate Limits
222
+
223
+ 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:
224
+
225
+ ```python
226
+ Client(account_sid="AC…", api_key="…", max_retries=4, timeout=60.0)
227
+ ```
228
+
229
+ ## πŸ› οΈ Development
230
+
231
+ ```bash
232
+ git clone https://github.com/voicetel/voiceml-python-sdk
233
+ cd voiceml-python-sdk
234
+ python -m venv .venv && source .venv/bin/activate
235
+ pip install -e ".[dev]"
236
+
237
+ # Unit tests (fast, no network)
238
+ pytest tests/unit
239
+
240
+ # Lint + type-check
241
+ ruff check src tests
242
+ mypy src
243
+
244
+ # Integration tests (live, read-only against a configured VoiceML instance)
245
+ cp .env.example .env # fill in VOICEML_ACCOUNT_SID / VOICEML_API_KEY / VOICEML_BASE_URL
246
+ pytest tests/integration
247
+
248
+ # Build wheel + sdist
249
+ python -m build
250
+ twine check dist/*
251
+ ```
252
+
253
+ ## πŸ“– API Documentation
254
+
255
+ - **Reference docs:** [voicetel.com/docs/api/v0.6/voiceml/](https://voicetel.com/docs/api/v0.6/voiceml/)
256
+ - **Validator:** [voicetel.com/voiceml/validator/](https://voicetel.com/voiceml/validator/)
257
+ - **SDK catalogue:** [voicetel.com/docs/voiceml-sdks/](https://voicetel.com/docs/voiceml-sdks/)
258
+ - **Type definitions:** see the `voiceml.models` module β€” every wire shape has a Pydantic model.
259
+
260
+ ## πŸ™Œ Contributors
261
+
262
+ - [Michael Mavroudis](https://github.com/mavroudis) β€” Lead Developer
263
+
264
+ Contributions welcome. Open an issue describing the change you want to make, or send a pull request against `main`.
265
+
266
+ ## πŸ’– Sponsors
267
+
268
+ | Sponsor | Contribution |
269
+ |---------|--------------|
270
+ | [VoiceTel Communications](https://voicetel.com) | Primary development and production hosting |
271
+
272
+ ## πŸ“„ License
273
+
274
+ MIT with the Commons Clause restriction. See [LICENSE](LICENSE) and [voicetel.com/legal/](https://voicetel.com/legal/).