Observed since 20 Oct, 15:29 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Python SDK for the 3CX XAPI (OData v4)
A typed Python SDK for the 3CX XAPI (/xapi/v1), built on httpx and Pydantic v2.
$filter, $select, $expand, $orderby, $top, $skip, $count)swagger.yaml, giving you full IDE autocomplete on every field of every entity (User has 60+ typed fields, Queue has 50+, etc.)client.get() / client.post()) for raw access to any endpointpip install 3cx-xapi-python-sdk
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())
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"].
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().
The SDK exposes 37 services as attributes of ThreeCXClient, covering every resource group in swagger.yaml.
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 |
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 |
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 |
client.<service> | Resource |
|---|---|
trunks | /Trunks, /TrunkTemplates, /Peers, /Sbcs |
phones | /PhoneTemplates, /SipDevices, /Fxs, /FxsTemplates, /DeviceInfos, /Firmwares |
client.<service> | Resource |
|---|---|
chat | /ChatHistoryView, /ChatMessagesHistoryView |
prompts | /PromptSets, /CustomPrompts, /Playlists |
email | /EmailTemplate |
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 |
client.<service> | Resource |
|---|---|
integrations | /Microsoft365Integration, /Microsoft365TeamsIntegration, /GoogleSettings, /AmazonIntegrationSettings, /DataConnectorSettings, /AISettings |
crm | /CrmIntegration, /CrmTemplates |
website_links | /WebsiteLinks |
client.<service> | Resource |
|---|---|
reports | 30+ report endpoints: call log, queue & extension statistics, agent login, activity log, SLA breaches, audit, scheduled reports |
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).
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 3.0.0 | 29 Sept 2026 | 8 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 15:29 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:c2427153c5701ee522bd75a20d06a2f701a2abafa698c9dce9f304dbaf8a0912