ai-telecom
Python SDK for AI Telecom — place AI phone calls with a number and a prompt.
Platform · API reference · OpenAPI
pip install ai-telecom
from ai_telecom import Client
client = Client(api_key="sk_live_...")
call = client.calls.create(
to="+84901234567",
prompt="""
Bạn là nhân viên tư vấn của MTDS.
Chào khách, hỏi xem anh chị có quan tâm gói internet không.
""",
)
print(call.id, call.status) # call_Ku3n8pQ7... queued
That is the whole getting-started. No campaign, no contact list, no workflow to build first — a phone number and a prompt is the complete API surface you need.
base_url defaults to https://telecom.mtds.vn; pass it only when developing
against a local deployment.
Calls
# Prompt inline
client.calls.create(to="+84901234567", prompt="...")
# Pick a voice
client.calls.create(to="+84901234567", prompt="...", voice="EXAVITQu4vr4xnSDxMaL")
# Reuse a saved agent
client.calls.create(to="+84901234567", agent_id="agent_9fQz...")
# Per-call script; keep the agent's voice / KB / config
client.calls.create(
to="+84901234567",
agent_id="agent_9fQz...",
prompt="Hôm nay chỉ nói về gói mùa hè…",
)
# Skip scoring / detach the agent's knowledge base for this call
client.calls.create(
to="+84901234567",
agent_id="agent_9fQz...",
evaluation="",
knowledge_base_id="",
)
# Many numbers at once → returns a list
client.calls.create(to=["+84901234567", "+84912345678"], agent_id="agent_9fQz...")
# Schedule for later, and attach your own IDs
client.calls.create(
to="+84901234567",
prompt="...",
scheduled_at="2026-08-01T02:00:00Z",
metadata={"crm_id": "C-42"},
)
The prompt is resolved in this order, first match wins:
inline prompt → inline agent → agent_id → your default agent.
Empty string "" on evaluation, knowledge_base, or knowledge_base_id
means explicitly off (do not inherit from the agent). Omit the field to inherit.
Whatever is resolved gets snapshotted onto the call, so editing an agent later never rewrites a call that already ran.
Reading results
call = client.calls.get("call_Ku3n8pQ7...")
call.status # completed | no_answer | failed | cancelled | queued | dialing | ringing | in_progress
call.duration # seconds of actual conversation (ringing excluded)
call.transcript # [TranscriptTurn(role="ai", text="..."), ...]
call.score # 0–100, only when you passed `evaluation`
call.outcome # potential | follow_up | not_interested
call.summary
call.usage.cost_usd # None means "no pricing configured", NOT free
call.usage.latency_ms # answer → first audio the customer hears
# Recording (signed URL, expires in 10 minutes)
client.calls.recording_url(call.id).url
Listing and filtering
client.calls.list(status="completed", score_min=70)
client.calls.list(status="dialing,ringing,in_progress") # anything live
client.calls.list(to="0901234567")
client.calls.list(from_="2026-07-01", until="2026-07-31")
# Walk every page without managing offsets yourself
for call in client.calls.iter(outcome="potential"):
print(call.to, call.score)
Cancelling
client.calls.cancel("call_Ku3n8pQ7...") # drops from queue, or hangs up mid-call
Waiting for a result
call = client.calls.wait("call_Ku3n8pQ7...", timeout=600)
print(call.score, call.summary)
wait() polls, which is fine for scripts. In production prefer the
call.completed webhook — a call can legitimately run for minutes.
Scoring a call
Pass evaluation and the platform scores the conversation after it ends:
client.calls.create(
to="+84901234567",
prompt="Bạn là nhân viên tư vấn...",
evaluation="""
Chấm 0–100:
- Khách có quan tâm sản phẩm không
- Khách có hỏi về giá không
- Khách có đồng ý hẹn gặp không
""",
)
Omit it and the call still runs — it simply returns no score, outcome or
summary, and skips the scoring model entirely.
Agents (optional)
An agent is a saved prompt + evaluation + voice. Use one when you want to reuse the same script across many calls.
agent = client.agents.create(
name="Fibre internet outreach",
prompt="Bạn là nhân viên tư vấn của MTDS...",
evaluation="Chấm 0–100 theo mức độ quan tâm.",
is_default=True, # now calls.create(to=...) works with no prompt at all
)
client.calls.create(to="+84901234567", agent_id=agent.id)
client.agents.list()
client.agents.update(agent.id, prompt="...")
client.agents.delete(agent.id)
Knowledge bases (optional)
A knowledge base is a collection of documents. Reference material the AI consults when the customer asks — a price list, an FAQ, product specs.
# Empty collection, then Markdown document
kb = client.knowledge_bases.create(name="Bảng giá 2026")
doc = client.knowledge_bases.documents.create(
kb.id,
name="Gói cước",
content="Gói Cơ bản: hai trăm nghìn đồng một tháng...",
)
# …or upload a file into the collection (PDF, DOCX, PPTX, XLSX, HTML, TXT, MD)
doc = client.knowledge_bases.documents.upload(kb.id, "bang-gia.pdf")
kb = client.knowledge_bases.wait(kb.id) # until processing_count == 0
# Shortcut: create + upload + wait
kb = client.knowledge_bases.create_with_upload("bang-gia.pdf", name="Bảng giá 2026")
if kb.failed_count:
print("Một số tài liệu lỗi — kiểm tra documents")
elif kb.is_ready:
for d in kb.documents or []:
print(d.name, d.status, d.effective_char_count)
full = client.knowledge_bases.documents.get(kb.id, kb.documents[0].id)
print(full.content)
for s in full.sections or []:
print(s.level, s.heading, s.char_count, s.enabled)
File upload returns immediately with document status="processing". Poll the
knowledge base with wait() (or get()) until processing_count == 0
before using it — otherwise calls.create returns knowledge_base_processing
or knowledge_base_unavailable.
wait() also returns when some documents failed (password-protected PDF,
unsupported format) — check failed_count / each document's error.
Attaching it
# To an agent…
client.agents.create(name="Sales", prompt="...", knowledge_base_id=kb.id)
# …or straight into one call
client.calls.create(to="+84901234567", prompt="...", knowledge_base_id=kb.id)
client.calls.create(to="+84901234567", prompt="...", knowledge_base="Giá: ...")
# Detach the agent's KB for one call
client.calls.create(to="+84901234567", agent_id=agent.id, knowledge_base_id="")
Size is a real cost
There is no retrieval step: the entire enabled content of the knowledge
base goes into the system prompt of every call that uses it.
estimated_tokens is therefore per-call cost, not a one-off. Disable documents
or sections you don't need in the dashboard — effective_char_count shows what
is actually sent.
Write numbers as words ("hai trăm nghìn", not "200000") — the text is spoken aloud, and digits are read unreliably.
Phone numbers
Read-only; numbers are provisioned by an administrator.
for number in client.phone_numbers.list():
print(number.number, number.status, number.quota_remaining)
client.calls.create(to="+84901234567", prompt="...", from_number="0592014235")
Each number carries its own concurrency limit, minute quota and
calling hours. A call created outside those hours is not rejected — it stays
queued and goes out when the window opens.
Usage
usage = client.usage.get() # last 30 days
usage = client.usage.get(from_="2026-07-01")
print(usage.total.calls, usage.total.minutes)
for day in usage.buckets:
print(day.date, day.calls, day.cost_usd)
Webhooks
Set a webhook URL on your API key in the dashboard. Every finished call is
POSTed to it, signed with HMAC-SHA256 in the X-Signature header.
from ai_telecom import webhooks
from ai_telecom.webhooks import WebhookVerificationError
@app.post("/hooks/ai-telecom")
def handle(request):
try:
event = webhooks.verify(
request.body, # RAW bytes — do NOT json.loads first
request.headers["X-Signature"],
secret=WEBHOOK_SECRET,
)
except WebhookVerificationError:
return 400
call = event.data # a full Call object
print(call.id, call.score, call.metadata)
return 200
Verification is constant-time. Always verify before trusting a payload.
Errors
from ai_telecom import (
AITelecomError, # base class — catches everything below
AuthenticationError, # 401
PermissionError_, # 403 — key lacks the scope
NotFoundError, # 404
ValidationError_, # 400 / 422
RateLimitError, # 429 — has .retry_after
ServerError, # 5xx
APIConnectionError, # network failure
)
try:
client.calls.create(to="not-a-number", prompt="...")
except ValidationError_ as e:
print(e.code, e) # invalid_phone `not-a-number` is not a valid phone number
Network errors and 429/5xx are retried automatically with backoff. Logic errors (401/403/404/422) are never retried — repeating them cannot change the answer.
Configuration
client = Client(
api_key="sk_live_...",
base_url="http://localhost:3000", # local development
timeout=30.0,
max_retries=2,
)
with Client(api_key="sk_live_...") as client: # closes the HTTP pool on exit
...
Versioning
This SDK is pinned to /api/v1. A breaking server change ships as /api/v2
alongside a major SDK version — v1 is never changed underneath an installed
client. Unknown response fields are ignored rather than raising, so an additive
server change never breaks an older SDK.
License
MIT © MTDS
Metadata
Release files for ai-telecom 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| ai_telecom-1.1.0.tar.gz | 95.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ai_telecom-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 116.7 kB
Release files / ai_telecom-1.1.0.tar.gz
| Download URL | ai_telecom-1.1.0.tar.gz |
|---|---|
| Size | 95.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
de647607ddfc02b69aed2db211d0bffdfffc90c71db90c1dd11418f2521acbfc
|
|
BLAKE2b-256 checksum How to use checksums |
305f901c72b7843441c151a780838a0c05094f5693693e7d3a8aab0ed4b6ed3e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / ai_telecom-1.1.0-py3-none-any.whl
| Download URL | ai_telecom-1.1.0-py3-none-any.whl |
|---|---|
| Size | 21.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5226eca530b3a84e6e3c115e70b4eaddb67b166162dfdd986b75a140a5bbe9f4
|
|
BLAKE2b-256 checksum How to use checksums |
73cb6181b25a354e9a99c8d9ad92e028b5aaa2b9962c5bd8c3dea3ef62e98a9d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Security for ai-telecom 1.1.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.