3CX Python SDK
A typed Python SDK for the 3CX XAPI (/xapi/v1), built on httpx and Pydantic v2.
Features
- OAuth2 client-credentials authentication with automatic token refresh
- OData v4 query builder (
$filter,$select,$expand,$orderby,$top,$skip,$count) - Automatic pagination — iterate over all pages with a single generator call
- Full coverage — 37 typed services covering every resource group in the 3CX XAPI swagger spec
- Auto-generated models — every Pydantic model is generated from
swagger.yaml, giving you full IDE autocomplete on every field of every entity (User has 60+ typed fields, Queue has 50+, etc.) - Reporting helpers for call log, queue performance, extension statistics, activity log, and more
- Context-manager support for clean resource management
- Escape hatches (
client.get()/client.post()) for raw access to any endpoint
Installation
pip install 3cx-xapi-python-sdk
Quick start
from threecx import ThreeCXClient, ODataQuery
client = ThreeCXClient(
base_url="https://pbx.example.com",
client_id="<service-principal-client-id>",
client_secret="<service-principal-secret>",
)
# ── Active calls ─────────────────────────────────────────────────────────────
for call in client.active_calls.list():
print(f"{call.caller} → {call.callee} ({call.status} since {call.established_at})")
# Drop a specific call
client.active_calls.drop(call_id=42)
# ── Users ─────────────────────────────────────────────────────────────────────
# List first 50 enabled users, sorted by extension number
q = ODataQuery().filter("Enabled eq true").order_by("Number").top(50)
for user in client.users.list(q):
print(user.number, user.full_name, user.email_address)
# Get a single user and update their e-mail
user = client.users.get(101)
client.users.update(101, {"EmailAddress": "new@example.com"})
# Create a user
from threecx.models import User
new_user = client.users.create(User(
Number="200",
FirstName="Alice",
LastName="Smith",
EmailAddress="alice@example.com",
))
# ── Queues ────────────────────────────────────────────────────────────────────
for queue in client.queues.list():
agents = client.queues.get_agents(queue.id)
print(f"Queue {queue.name}: {len(agents)} agents")
# ── Call history ──────────────────────────────────────────────────────────────
q = ODataQuery().filter("SrcExternal eq true").order_by("SegmentStartTime desc").top(100)
for entry in client.call_history.iterate(q):
print(entry.segment_start_time, entry.src_caller_number, "→", entry.dst_caller_number, entry.call_answered)
# ── Reports ───────────────────────────────────────────────────────────────────
from datetime import datetime, timedelta, timezone
end = datetime.now(timezone.utc)
start = end - timedelta(days=7)
rows = client.reports.get_call_log(start, end)
print(f"{len(rows)} call-log entries for the last 7 days")
# ── System status ─────────────────────────────────────────────────────────────
status = client.system.get_status()
print(f"PBX {status.version} calls: {status.calls_active} disk: {status.disk_usage}%")
# ── Context manager ───────────────────────────────────────────────────────────
with ThreeCXClient(base_url="https://pbx.example.com",
client_id="...", client_secret="...") as c:
print(c.system.get_license())
OData query builder
from threecx import ODataQuery
q = (
ODataQuery()
.filter("LastName eq 'Smith'")
.select("Id", "Number", "FirstName", "LastName", "Email")
.expand("Groups")
.order_by("Number")
.top(25)
.skip(0)
.count()
)
All query parameters map directly to OData v4 syntax. Refer to the 3CX XAPI swagger for supported fields per endpoint.
Every method whose endpoint documents query options takes query=, including single-entity and settings getters
($select/$expand), e.g. client.settings.get_mail_settings(query=ODataQuery().select("MailServer")). list() methods
return the items of one page; read $count with client.call_history.count(q), or through the escape hatch:
client.get("/Users", params=ODataQuery().count().top(0).to_params())["@odata.count"].
Calling actions
3CX actions (POST …/Pbx.Name) take named parameters, and the SDK methods take the same parameters as arguments. Pass
the object itself; the method posts it under the parameter name 3CX expects (InstallUpdates wants
{"install": {...}}, SetUpdateSettings wants {"settings": {...}}, and so on).
from threecx.models import InstallUpdates, XServiceManageOptions
# Install every available update
available = client.updates.get_updates()
client.updates.install_updates(InstallUpdates(
key=available["Key"],
entries=[item["Guid"] for item in available["Entries"]],
))
# Restart a PBX service
client.pbx_services.restart(XServiceManageOptions(service_names=["3CXQueueManager"]))
# A dict with the API's PascalCase keys works wherever a model does
client.pbx_services.stop({"ServiceNames": ["3CXQueueManager"]})
Object arguments accept the model from threecx.models or a plain dict; enum arguments accept the enum or its string
value. Actions without parameters take no body argument: client.recordings.archive().
Creating a service principal
- Log in to 3CX as admin.
- Navigate to Admin → Integrations → API.
- Create a new service principal and copy the Client ID and Client Secret.
Services reference
The SDK exposes 37 services as attributes of ThreeCXClient, covering every resource group in swagger.yaml.
Calls & telephony
client.<service> |
Resource |
|---|---|
active_calls |
/ActiveCalls — list & drop live calls |
call_history |
/CallHistoryView — paginated call log |
recordings |
/Recordings, /RemoteArchivingSettings |
voicemail |
/VoicemailSettings, /MusicOnHoldSettings |
fax |
/Fax, /FaxServerSettings |
Users, groups & directories
client.<service> |
Resource |
|---|---|
users |
/Users — CRUD extensions/users + greetings, phones, provisioning |
my_user |
/MyUser — current authenticated user |
groups |
/Groups — full admin groups (members, rights, restrictions) |
my_group |
/MyGroup — current user's primary group |
contacts |
/Contacts — company phone book |
parameters |
/TenantProperties, /DNProperties, directory info |
Call routing & flow
client.<service> |
Resource |
|---|---|
queues |
/Queues — CRUD call queues & agents |
ring_groups |
/RingGroups — CRUD ring groups |
inbound_rules |
/InboundRules — DID/inbound rules |
outbound_rules |
/OutboundRules — outbound dial rules |
receptionists |
/Receptionists — digital receptionists |
holidays |
/Holidays, /OfficeHours |
parkings |
/Parkings, /CallParkingSettings |
call_flow |
/CallFlowApps, /CallFlowScripts |
Trunks & PBX hardware
client.<service> |
Resource |
|---|---|
trunks |
/Trunks, /TrunkTemplates, /Peers, /Sbcs |
phones |
/PhoneTemplates, /SipDevices, /Fxs, /FxsTemplates, /DeviceInfos, /Firmwares |
Communication
client.<service> |
Resource |
|---|---|
chat |
/ChatHistoryView, /ChatMessagesHistoryView |
prompts |
/PromptSets, /CustomPrompts, /Playlists |
email |
/EmailTemplate |
System administration
client.<service> |
Resource |
|---|---|
system |
/SystemStatus, /LicenseStatus, /Parameters |
settings |
/GeneralSettingsForPbx, /MailSettings, /CDRSettings, ~20 settings domains |
pbx_services |
/Services — start/stop/enable/disable PBX services |
backups |
/Backups, failover & restore settings |
updates |
/GetUpdates(), /InstallUpdates, update settings, prompt set / CRM updates, purge |
event_logs |
/EventLogs |
security |
/SecurityTokens, /ServicePrincipals, /BlackListNumbers, /Blocklist, /AntiHackingSettings, /Firewall |
emergency |
/EmergencyGeoLocations, /EmergencyNotificationsSettings |
defs |
/Defs — codecs, timezones, gateway parameters, countries, DID numbers |
Integrations
client.<service> |
Resource |
|---|---|
integrations |
/Microsoft365Integration, /Microsoft365TeamsIntegration, /GoogleSettings, /AmazonIntegrationSettings, /DataConnectorSettings, /AISettings |
crm |
/CrmIntegration, /CrmTemplates |
website_links |
/WebsiteLinks |
Reporting
client.<service> |
Resource |
|---|---|
reports |
30+ report endpoints: call log, queue & extension statistics, agent login, activity log, SLA breaches, audit, scheduled reports |
Escape hatch
Any endpoint not covered by a typed service can be called directly:
data = client.get("/InboundRules", params={"$top": "10"})
client.post("/Backups/Pbx.Backup", json={"description": {"Name": "before-upgrade"}})
client.post() sends json exactly as given, so key an action's body by its parameter names yourself (look them up
in swagger.yaml under the action's …RequestBody schema).
Metadata
Release files for 3cx-xapi-python-sdk 3.0.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 | |
|---|---|---|---|
| 3cx_xapi_python_sdk-3.0.0.tar.gz | 101.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 3cx_xapi_python_sdk-3.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 204.4 kB
Release files / 3cx_xapi_python_sdk-3.0.0.tar.gz
| Download URL | 3cx_xapi_python_sdk-3.0.0.tar.gz |
|---|---|
| Size | 101.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
898143456978fad89f57b20681e90c1e802e6c5231244f20e3ed88facb8d9966
|
|
BLAKE2b-256 checksum How to use checksums |
2084aebcdc9632272ddd5610096aaac4f7506a2d7878b50ad9b636fbf1776a40
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.20 {"installer":{"name":"uv","version":"0.12.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / 3cx_xapi_python_sdk-3.0.0-py3-none-any.whl
| Download URL | 3cx_xapi_python_sdk-3.0.0-py3-none-any.whl |
|---|---|
| Size | 102.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a929d04c08239514f73c0dde3b309ec613fd3500ea982c9136246e054b917388
|
|
BLAKE2b-256 checksum How to use checksums |
ec8f88f28b90ebd96b7c32feac726ab5e3b85a674b698be019180a63dd769712
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.20 {"installer":{"name":"uv","version":"0.12.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|