📞 Simotel Connect (Python & Django Client for Simotel PBX API v4)
A professional, modern, and reusable Python and Django client library for communicating with the Simotel PBX API (v4).
🇮🇷 برای مطالعه مستندات به زبان فارسی کلیک کنید (Persian Documentation)
🌟 Features
- 🔐 Full Dual Authentication Support: Seamlessly manages both
X-APIKEYand HTTP Basic Auth headers required by Simotel. - ⚙️ Flexible 3-Tier Configuration Chain:
Direct arguments>Django settings.SIMOTEL>Environment variables (.env) - 🏗️ Modular Facade & Manager Pattern: Clean, categorized, and intuitive API namespaces (
sm.call,sm.pbx,sm.reports,sm.autodialer, etc.). - 🔄 Session Pooling & Auto Retry: Stable connection management with
urllib3retry adapters. - 🛡️ Comprehensive Exception Hierarchy: Clear error types for auth failures, network issues, timeouts, and PBX API errors.
- 📊 Over 80+ Simotel Endpoints Supported:
- Call Origination (Click-to-Call): Initiate calls via
POST /call/originate/act - PBX Users & Extensions: Add, search, edit, and remove PBX users
- Queues & Agents: Queue management, single and batch agent assignment, pause/resume
- Trunks: Trunk configuration and status
- Blacklists & Whitelists: Block or permit numbers with ease
- Announcements & Music on Hold: Upload and manage audio files
- Faxes: Send, receive, and download incoming/outgoing faxes
- Voicemails: Download voicemail recordings and manage boxes
- Reports & CDR: Search call detail records, queue stats, agent performance, and download single/dual-channel audio recordings
- Autodialer: High-volume automated outbound calling campaigns and contact groups
- System Health: Ping and connectivity verification
- Call Origination (Click-to-Call): Initiate calls via
📦 Installation
Install the package via pip:
pip install simotel-connect
For development and local testing:
git clone git@github.com:mraminrzn/simotel-connect.git
cd simotel-connect
pip install -e ".[dev]"
⚙️ Configuration
Option 1: Django settings.py (Recommended for Django projects)
Add simotel_connect to your INSTALLED_APPS and define the SIMOTEL dictionary:
# settings.py
INSTALLED_APPS = [
...,
"simotel_connect",
]
SIMOTEL = {
"HOST": "192.168.1.10", # Simotel IP address or hostname
"API_KEY": "YOUR_SIMOTEL_API_KEY", # From Maintenance > API Accounts
"USERNAME": "admin", # Simotel web panel username
"PASSWORD": "your_password", # Simotel password
"PORT": 80, # Optional (default: 80)
"SCHEME": "http", # Optional: "http" or "https" (default: "http")
"TIMEOUT": 30, # Optional: request timeout in seconds (default: 30)
"VERIFY_SSL": True, # Optional (default: True)
}
Then use the client anywhere in your project (views, services, Celery tasks):
from simotel_connect import Simotel
# Automatically reads configuration from settings.py
sm = Simotel()
Option 2: Environment Variables (.env)
SIMOTEL_HOST=192.168.1.10
SIMOTEL_API_KEY=YOUR_SIMOTEL_API_KEY
SIMOTEL_USERNAME=admin
SIMOTEL_PASSWORD=your_password
SIMOTEL_TIMEOUT=30
SIMOTEL_VERIFY_SSL=false
from simotel_connect import Simotel
sm = Simotel()
Option 3: Direct Initialization (Standalone scripts or multi-tenant PBX)
from simotel_connect import Simotel
sm = Simotel(
host="192.168.1.10",
api_key="YOUR_SIMOTEL_API_KEY",
username="admin",
password="your_password",
timeout=20,
verify_ssl=False,
)
🚀 Usage & Examples
1. Connection Health Check (Ping)
response = sm.setting.ping()
if response.success:
print("Successfully connected to Simotel:", response.message)
2. Call Origination (Click-to-Call / Two-Way Calls)
Simotel's Call Origination initiates a two-way phone call via POST /call/originate/act:
- Simotel first dials the
caller(e.g., an internal extension1001or operator softphone). - Once the caller picks up, Simotel bridges the channel and dials the
callee(e.g., a customer's external mobile0912...or another extension) via the specified dialplancontext. - Simotel returns a job confirmation and a tracking identifier
originated_call_id.
A. Agent to External Customer (Two-Way Call)
# Operator 1001 calls an external mobile number
resp = sm.call.originate(
caller="1001", # Agent internal extension
callee="09121234567", # Customer phone number
context="default", # Outgoing dialplan context
caller_id="1001", # Caller ID displayed to the customer
timeout=30, # Ringing timeout in seconds
)
if resp.success:
call_job_id = resp.data.get("originated_call_id")
print(f"Call initiated successfully! Job ID: {call_job_id}")
B. Extension to Extension (Internal Two-Way Call)
resp = sm.call.originate(
caller="1001",
callee="1002",
context="default",
)
C. Automated Robocall / Direct Voice Broadcast (No Human Agent)
To play a pre-recorded message or IVR menu directly to a customer without requiring an agent's handset to ring first:
# Originate directly into a Simotel Announcement or IVR context
resp = sm.call.originate(
caller="*8001", # Shortcode or extension mapped to an Announcement/IVR in Simotel Dialplan
callee="09121234567", # Customer number
context="default",
caller_id="02188888888", # Company caller ID number
timeout=35,
)
3. Users & Extensions Management
# Create a new extension
sm.pbx.users.add(
extension="1001",
name="John Doe",
password="StrongPassword123",
email="john@example.com",
mobile="09121234567",
)
# Search users
users = sm.pbx.users.search(extension="1001")
print(users.data)
# Update a user
sm.pbx.users.update(extension="1001", name="John Doe (Support Lead)")
# Remove a user
sm.pbx.users.remove(extension="1001")
4. Queues & Call Center Agents
# Create a call queue
sm.pbx.queues.add(name="2000", strategy="leastrecent", timeout=30)
# Add an agent to the queue
sm.pbx.queues.add_agent(queue="2000", agent="1001", penalty=0)
# Pause agent (e.g. lunch break)
sm.pbx.queues.pause_agent(queue="2000", agent="1001", reason="Lunch break")
# Resume agent
sm.pbx.queues.resume_agent(queue="2000", agent="1001")
# Batch add agents to queue
sm.pbx.queues.batch_add_agent(queue="2000", agents=["1001", "1002", "1003"])
# Batch pause agents
sm.pbx.queues.batch_pause_agent(queue="2000", agents=["1001", "1002"])
# Remove agent from queue
sm.pbx.queues.remove_agent(queue="2000", agent="1001")
5. Call Detail Records (CDR), Call Status & Audio Downloads
Query, filter, and track call histories and download recorded voice calls via sm.reports:
A. Smart CDR Search & Filtering
# Search calls by caller, callee, date range, or call ID (cuid)
response = sm.reports.cdr_search(
conditions={
"from": "1001", # Caller extension or number
"to": "09121234567", # Destination number
"disposition": "ANSWERED", # ANSWERED | NO ANSWER | BUSY | FAILED
},
date_range={
"from": "2026-09-01 08:00",
"to": "2026-09-30 20:00",
},
pagination={"start": 0, "count": 25, "sorting": {"starttime": -1}},
alike="true", # Enable fuzzy/partial matching
)
for call in response.data:
print(f"CUID: {call.get('cuid')}")
print(f"Start Time: {call.get('starttime')} | Disposition: {call.get('disposition')}")
print(f"Total Duration: {call.get('duration')}s | Talk Time (billsec): {call.get('billsec')}s")
print(f"Recorded File: {call.get('record') or call.get('audio_file')}")
print("-" * 40)
B. Quick Search by Unique Call ID (cuid)
# Check status of a specific call by its Simotel unique ID
res = sm.reports.cdr_search(conditions={"cuid": "cuid-1726478923.412"})
if res.data:
call_info = res.data[0]
is_answered = call_info.get("disposition") == "ANSWERED"
talk_seconds = call_info.get("billsec", 0)
print(f"Answered: {is_answered}, Talk time: {talk_seconds}s")
C. Download Call Audio Recordings (.mp3 / .wav)
# 1. Download single-channel recording
audio_bytes = sm.reports.download_audio(file="20260914_v2.1789362850.25437.mp3")
with open("recorded_call.mp3", "wb") as f:
f.write(audio_bytes)
# 2. Download dual-channel recording (agent and customer separated left/right)
dual_bytes = sm.reports.download_audio_dual_channel(file="20260914_v2.1789362850.25437.mp3")
with open("dual_channel.mp3", "wb") as f:
f.write(dual_bytes)
6. Blacklists & Whitelists
# Block unwanted incoming number
sm.pbx.blacklists.add(number="09999999999", description="Spam caller")
# Remove from blacklist
sm.pbx.blacklists.remove(number="09999999999")
# Add VIP contact to whitelist
sm.pbx.whitelists.add(number="09120000000", description="Executive VIP")
7. Autodialer & Automated Robocall Campaigns (Simotel v4)
Simotel Autodialer enables mass outbound campaigns, playing pre-recorded audio messages without human intervention. The client supports the complete official Simotel v4 API workflow:
Step 1: Discover Trunks & Announcements
# 1. Query Autodialer & PBX Trunks to get trunk_manager_id
trunks = sm.autodialer.trunks.search().data
# [{"_id": "6033876dc92de036d1390923", "name": "SipTrunk_Main", "channels": 30, ...}]
trunk_id = trunks[0]["_id"]
# 2. Query available Audio Announcements
announcements = sm.autodialer.announcements.search().data
# [{"_id": "653e0c4f95e63077f8379be7", "name": "Welcome_Offer", ...}]
announcement_id = announcements[0]["_id"]
Step 2: Create a Contact Group (Optional)
Groups allow organizing target contact lists in Simotel:
# Create group with contact numbers in a single call
group_res = sm.autodialer.groups.add(
name="VIP_Customers",
numbers=["09121111111", "09122222222", "09123333333"],
description="VIP autumn outreach list",
)
group_id = group_res.data.get("_id")
Step 3: Launch Campaign
Option A: Launch using Contact Group
camp_res = sm.autodialer.campaigns.add(
name="Autumn_Promotion",
groups=[group_id], # Simotel Group ObjectId
trunk_manager_id=trunk_id, # Trunk ObjectId
announcement=announcement_id, # Audio Announcement ObjectId
start="2026-09-16 09:00", # Start datetime (YYYY-MM-DD HH:MM)
end="2026-09-23 20:00", # End datetime
count=3, # Number of recipients
try="2", # Max retry attempts for unanswered calls
try_interval="600", # Interval between retries in seconds
interface_context="auto",
interface_text="text1",
description="Automated promo voice call",
)
campaign_id = camp_res.data.get("_id")
print(f"Campaign launched successfully! ID: {campaign_id}")
Option B: Direct Campaign without Creating a Group
Simotel v4 allows passing phone numbers directly to the campaign:
camp_res = sm.autodialer.campaigns.add(
name="Instant_Alert_Campaign",
numbers=["09121111111", "09122222222"],
groups=[], # Empty groups list
trunk_manager_id=trunk_id,
announcement=announcement_id,
start="2026-09-16 09:00",
end="2026-09-17 18:00",
count=2,
try="1",
)
Step 4: Campaign Reports & Asterisk Cause Inspection
Track real-time call dispositions and troubleshoot why calls might fail:
# Fetch reports for the campaign
rep_res = sm.autodialer.reports.search(
conditions={"campaign_id": campaign_id},
pagination={"start": 0, "count": 50, "sorting": {"date": -1}}
)
for report in rep_res.data:
number = report.get("number")
disposition = report.get("disposition") # ANSWERED, NO ANSWER, BUSY, FAILED
billsec = report.get("billsec", 0) # Talk duration in seconds
print(f"Number: {number} | Status: {disposition} | Talk: {billsec}s")
# If call failed, inspect Asterisk cause details:
tries = report.get("tries", [])
if tries:
last_try = tries[-1]
cause_txt = last_try.get("cause-txt") # e.g. "Circuit/channel congestion", "Call Rejected"
cause_code = last_try.get("cause")
if cause_txt:
print(f" --> Cause: {cause_txt} (Asterisk Code: {cause_code})")
Step 5: Cleanup & Removing Campaigns & Groups
# Remove Campaign by ObjectId
sm.autodialer.campaigns.remove(_id=campaign_id)
# Remove Group by ObjectId
sm.autodialer.groups.remove(_id=group_id)
⚠️ Exception Handling
All custom exceptions inherit from SimotelError:
from simotel_connect import (
Simotel,
SimotelError,
SimotelAuthError,
SimotelAPIError,
SimotelConnectionError,
SimotelTimeoutError,
)
sm = Simotel()
try:
sm.call.originate(caller="1001", callee="1002")
except SimotelAuthError:
print("Authentication failed: Check API Key, username, or password.")
except SimotelAPIError as e:
print(f"Simotel returned an API error: {e.args[0]}")
print(f"Details: {e.data}")
except SimotelConnectionError:
print("Connection error: Unable to reach Simotel server.")
except SimotelTimeoutError:
print("Request timed out.")
except SimotelError as e:
print(f"Simotel error: {e}")
🖥️ Interactive Web Demo
An interactive Django test panel is provided under demo_project/. It allows you to visually test audio downloading and call origination:
cd demo_project
python manage.py runserver
Open http://127.0.0.1:8000/ in your browser.
🧪 Running Tests
pip install -e ".[dev]"
pytest
📄 License
This project is licensed under the MIT License.
Metadata
Release files for simotel-connect 1.0.6
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 | |
|---|---|---|---|---|
| simotel_connect-1.0.6.tar.gz | 37.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| simotel_connect-1.0.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 84.3 kB
Release files / simotel_connect-1.0.6.tar.gz
| Download URL | simotel_connect-1.0.6.tar.gz |
|---|---|
| Size | 37.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6ae0804564137e079d98117e90f3ecc950628676ff459e160d90c2c04a311df4
|
|
BLAKE2b-256 checksum How to use checksums |
985e1303ac18f6921ab4875f622b3270a8f4cff9775836d336b91625b6077157
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|
Release files / simotel_connect-1.0.6-py3-none-any.whl
| Download URL | simotel_connect-1.0.6-py3-none-any.whl |
|---|---|
| Size | 46.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6c2c96c467cd43f7d9809f027a12cb764b40989c02d37c3e49d61c6dd2e23585
|
|
BLAKE2b-256 checksum How to use checksums |
a5572018ca7642bb4e19bbe9e55501940fe0f18f9222ed5fb17bb68ed8840c21
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|
Security for simotel-connect 1.0.6
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.