Skip to main content

📞 Simotel Connect (Python & Django Client for Simotel PBX API v4)

PyPI version Python Versions Django License: MIT

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-APIKEY and 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 urllib3 retry 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

📦 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

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:

  1. Simotel first dials the caller (e.g., an internal extension 1001 or operator softphone).
  2. Once the caller picks up, Simotel bridges the channel and dials the callee (e.g., a customer's external mobile 0912... or another extension) via the specified dialplan context.
  3. 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)

Source distribution for simotel-connect 1.0.6
File Size Uploaded Provenance
simotel_connect-1.0.6.tar.gz 37.8 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for simotel-connect 1.0.6
File Interpreter ABI Platform
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.

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