Skip to content

Configuration Recipes

Common provisioning workflows using axltoolkit.

Provision a New User with a Phone

from axltoolkit import AXLClient, PhoneBuilder

client = AXLClient(
    username="admin",
    password="secret",
    server_ip="ucm-pub.example.com",
    version="15.0",
    tls_verify=True,
)

# 1. Add a directory number (line)
client.add_line(line={
    "pattern": "1001",
    "routePartitionName": "PT_Internal",
    "description": "John Smith",
    "alertingName": "John Smith",
    "asciiAlertingName": "John Smith",
})

# 2. Add the phone using the builder
phone = (
    PhoneBuilder("SEP001122334455", product="Cisco 8845")
    .device_pool("Default")
    .sip_profile("Standard SIP Profile")
    .security_profile("Cisco 8845 - Standard SIP Non-Secure Profile")
    .phone_template("Standard 8845 SIP")
    .description("John Smith - 1001")
    .owner("jsmith")
    .add_line(1, "1001", "PT_Internal", display="John Smith")
    .build()
)
client.add_phone(phone)

# 3. Create the end user
client.add_user(user={
    "userid": "jsmith",
    "firstName": "John",
    "lastName": "Smith",
    "password": "changeme123",
    "pin": "12345",
    "associatedDevices": {"device": ["SEP001122334455"]},
    "primaryExtension": {
        "pattern": "1001",
        "routePartitionName": "PT_Internal",
    },
    "enableCti": True,
})

# 4. Apply the phone to push config
client.apply_phone(name="SEP001122334455")

Bulk Update Phone Descriptions

# Get all SEP phones via SQL
result = client.sql_query("SELECT name FROM device WHERE name LIKE 'SEP%'")

for row in result.get("rows", []):
    phone_name = row["name"]
    try:
        client.update_phone(name=phone_name, description=f"Auto-labeled: {phone_name}")
        print(f"Updated {phone_name}")
    except Exception as e:
        print(f"Failed to update {phone_name}: {e}")

Export Registered Phones to CSV

import csv
from axltoolkit import RISPortClient

ris = RISPortClient("admin", "password", "ucm-pub.example.com", tls_verify=True)

phones = ris.get_registered_phones("SEP*")

with open("registered_phones.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["name", "ip_address", "status", "model", "node"])
    writer.writeheader()
    writer.writerows(phones)

print(f"Exported {len(phones)} phones")

Monitor Call Volume with PerfMon

import time
from axltoolkit import PerfMonClient

pm = PerfMonClient("admin", "password", "ucm-pub.example.com", tls_verify=True)

session = pm.open_session()
pm.add_counters(session, [
    r"\\cm-pub\Cisco CallManager\CallsActive",
    r"\\cm-pub\Cisco CallManager\CallsCompleted",
])

try:
    for _ in range(10):
        data = pm.collect_session_data(session)
        if data:
            for host, objects in data.items():
                for obj_name, obj_data in objects.items():
                    counters = obj_data.get("counters", {})
                    print(f"[{host}] {obj_name}: {counters}")
        time.sleep(5)
finally:
    pm.close_session(session)

Check UCM Version and Cluster Topology

from axltoolkit import PAWSClient

paws = PAWSClient("platformadmin", "secret", "ucm-pub.example.com", tls_verify=True)

version = paws.get_active_version()
print(f"UCM Version: {version}")

nodes = paws.get_cluster_nodes()
print(f"Cluster Nodes: {nodes}")

hw = paws.get_hardware_information()
print(f"Hardware: {hw}")

Restart a Service

from axltoolkit import ServiceabilityClient

svc = ServiceabilityClient("platformadmin", "secret", "ucm-pub.example.com", tls_verify=True)

# Check current status
status = svc.get_service_status(["Cisco CallManager"])
print(status)

# Restart the service
svc.restart_service("Cisco CallManager")

Add a SIP Trunk with Multiple Destinations

from axltoolkit import AXLClient, SipTrunkBuilder

client = AXLClient("admin", "password", "ucm-pub.example.com", version="15.0")

trunk = (
    SipTrunkBuilder("SIP-Trunk-ITSP")
    .device_pool("Default")
    .security_profile("Non Secure SIP Trunk Profile")
    .sip_profile("Standard SIP Profile")
    .calling_search_space("CSS_Trunk_Outbound")
    .description("ITSP SIP trunk with failover")
    .add_destination("sbc-primary.example.com", 5060)
    .add_destination("sbc-secondary.example.com", 5060)
    .run_on_every_node(True)
    .build()
)

client.add_sip_trunk(trunk)

Build a Calling Search Space

from axltoolkit import AXLClient, CssBuilder

client = AXLClient("admin", "password", "ucm-pub.example.com", version="15.0")

# Create partitions first
for pt_name in ["PT_Internal", "PT_Local", "PT_LongDistance"]:
    client.add_route_partition(routePartition={"name": pt_name})

# Build and add the CSS
css = (
    CssBuilder("CSS-Full-Access")
    .description("Internal + Local + Long Distance")
    .add_partition("PT_Internal")
    .add_partition("PT_Local")
    .add_partition("PT_LongDistance")
    .build()
)

client.add_css(css)

Using the Direct Service Proxy

For AXL operations not yet wrapped by a dedicated method, use the .service property to access the raw zeep service proxy:

# Direct zeep call for an uncommon operation
result = client.service.doLdapSync(name="LDAP_Directory_1", sync=True)

Debugging Requests

Enable request/response logging for troubleshooting:

import logging

# Turn on axltoolkit debug logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("axltoolkit").setLevel(logging.DEBUG)

# Or inspect the last request/response
client.get_phone(name="SEP001122334455")
debug = client.last_request_debug()
print(debug["request"]["envelope"])   # SOAP XML sent
print(debug["response"]["envelope"])  # SOAP XML received