The next few sections will take you through the steps implement a variety of capabilities allowing your portal to
interact with Webex APIs. To achieve these goals, you will start with the following steps:
Add your Service App Client ID, Secret, and Refresh Token to the Environment.
Look up the user by email, get their user object ID and, with that, look up the user's details,
including all Webex Calling information.
Retrieve all licenses, then find the object ID of a Webex Calling and On-prem UCM licenses.
Webex Calling users must be associated with a Location, so the object ID of a desired location must
be retrieved.
Update the user, modifying the licenses and updating their location.
Step 1 - Add your Service App Client ID, Secret, and Refresh Token to the Environment
In the example you completed earlier, you obtained an access token using a Service App client ID,
client secret, and refresh token. This enabled administrative access to Webex.
In this section, you will use the information you obtained previously to enable your Portal to perform administrative
tasks in Webex as well.
Now you can start using the wxc_sdk in your project.
In your VS Code
tab, open up flaskr/api/v1/wbxc.py using the Explorer.
In the earlier examples, you ran the service_app.py script to create an access token,
which you then manually pasted into the wxc_enable_user.py file.
The access token was then used to instantiate a wxc_sdk instance to be able to access the Webex APIs.
api = WebexSimpleApi(tokens=access_token)
To handle this automatically, we have created a class:
classServiceApp:def__init__(self):"""
A Service App
"""
self.tokens = self.get_tokens()
self.api = WebexSimpleApi(tokens=self.tokens)
In this code, self.get_tokens() will either read a set of access/refresh tokens from a file, or use the
environment variables you set above. If an access token is about to expire or isn't found to begin with,
then the refresh token will be used to create a new access token, as you did in the example. The access
token is then used to create an instance of the API.
By placing this token refresh capability in this class, each time a new instance of this ServiceApp class
is instantiated, the token may be refreshed, if needed, as long as the refresh token has not expired.
To create an instance of the ServiceApp class, you just need this single line of code:
sa = ServiceApp()
In previous examples, you had created a variable named api which contained an instance
of the WebexSimpleApi class and used it to perform API calls like this:
You will be using the sa instance of the ServiceApp class to perform the same
API calls. The ServiceApp() class calls the WebexSimpleApi class for you but before
doing so, it will check if the access token is still valid and refresh it if needed. You can perform the same
people list call like this:
Note the use of sa.api.people.list() instead of
api.people.list().
If you would like to explore in more detail how the ServiceApp() class works, you can look at the
code in the flaskr/api/v1/service_app/__init__.py file.
Now you are ready to add to the portal code.
Remove the pass from the section in get_person_det and replace with the following:
defget_person_det(email):"""
Searches the Webex Persons by email address, if exactly one is found, look up and return
the Person details, including Webex Calling Data.
"""# Create a Service App instance
sa = ServiceApp()# Search all users and find the one containing the email
wxc_people_list =list(sa.api.people.list(email=email))iflen(wxc_people_list)==1:# Look up user details, including Webex Calling data; the wxc_people_list has exactly one entry at index[0]
my_person = sa.api.people.details(person_id=wxc_people_list[0].person_id, calling_data=True)return my_person
The get_person_det() function takes an email address as an argument and returns the
detailed user information for a user with that email address.
This code is very similar to what you did in the previous examples. It creates a new instance of the
ServiceApp class, then uses it to look up a user using the people.list() API and then
retrieves the detailed user information using the people.details() API.
The wbxc.py file already contains a class named wbxc_user_api that will be called when the
/wbxc/user/<userid> API is called on your Portal API. Extend it to
report success or failure based on the get_person_det() results. Any
RestError exceptions that may be generated by the wxc_sdk are already handled.
@api.route("/user/<string:userid>")@api.param('userid','The user id of the user to be returned')classwbxc_user_api(Resource):defget(self, userid):"""
Retrieve a user in WbxC by user id.
"""try:# Get the detailed user by email
target_person = get_person_det(to_email(userid))ifnot target_person:return jsonify({'success':False,'message':f'User not found with email {to_email(userid)}'})return jsonify({'success':True,'message':f'Successfully found user {userid}','user_data': target_person.dict()})except Exception as e:# Return any API error that may have been raisedreturn jsonify({'success':False,'message':str(e)})
Now if your Portal API receives a request on the /user/<userid> endpoint, it will look up that user in Webex
and return the result as JSON. If the user is not found, it will return an error message.
Step 3 - Enabling a User for Webex Calling
This section will add the code to enable a user for Webex Calling.
Find the class wbxc_user_enable_api() section in the flaskr/api/v1/wbxc.py file. You
will be updating the put method.
The put accespt a single argument, the userid which is the user's email address.
The first step to enabling a user for Webex Calling is to get the detailed user
information by calling
get_person_det() method that you just implemented, then look up the Webex Calling Professional and
On-Prem UCM licenses. Since this is also a common task, a get_lic_by_name() function was
created to look up the respective license in the same way you did in the earlier example sections.
Next, query the Location to which this user and number will be assigned. Following this, look
up the user's phone number, which should already be pre-populated based on what was synchronized
to the account from the Active Directory via the Directory Connector. If no number is found
for the user, return an error. This is an area that could require a lot of
custom enhancements. For example, you could use APIs to look for available numbers in the location and assign
the next available number to the user.
Add the following to your code:
classwbxc_user_enable_api(Resource):@api.expect(wbxc_enable_user_args, validate=True)defput(self, userid):"""
Enable a user for Webex Calling given a user ID, a Webex Calling location and a Webex Calling License name.
The phone number will be read from the user's Webex attributes (synched from AD/Directory Connector)
"""try:# Read all arguments
args = wbxc_enable_user_args.parse_args(request)# Get the person details for a user with this email address
target_person = get_person_det(to_email(userid))ifnot target_person:return jsonify({'success':False,'message':f'User not found with email {to_email(userid)}'})# Create a Service App instance
sa = ServiceApp()# Search all licenses and find the one matching the specified license
license_list =list(sa.api.licenses.list())
wxc_pro_license = get_lic_by_name(licenses=license_list, name='Webex Calling - Professional')
ucm_license = get_lic_by_name(licenses=license_list, name='Unified Communication Manager (UCM)')# Find the location provided location name
target_location = sa.api.locations.by_name(args['location'])ifnot target_location:return jsonify({'success':False,'message':f'Location not found: {args["location"]}'})# Look up the user's phone number (set in Active Directory and synched to the Webex work number)
target_tn =Nonefor num in target_person.phone_numbers:if num.number_type =='work':
target_tn = num.value
ifnot target_tn:return jsonify({'success':False,'message':f'Phone number not configured for user: {userid}'})# Check if user is already enabled for Webex Calling (i.e. they have a location_id assigned)
Check to see if the user is already enabled for Webex Calling and if they are not, prepare the user
by adding the Webex Calling Professional license and removing the On-prem UCM license, then set
the location and update the person.
Add the following to your code and then examine it. The comments in the code explain what each step does.
# Check if user is already enabled for Webex Calling (i.e. they have a location_id assigned)ifnot target_person.location_id:# Append the Webex license and remove the Unified Communication Manager (UCM) one, if present
target_person.licenses.append(wxc_pro_license.license_id)if ucm_license.license_id in target_person.licenses:
target_person.licenses.remove(ucm_license.license_id)# Set the user's location id
target_person.location_id = target_location.location_id
# Update the user
sa.api.people.update(person=target_person, calling_data=True)return jsonify({'success':True,'message':f"Successfully enabled Webex Calling for user {userid} on "f"location {args['location']} with number {target_tn}",'user_data': target_person.dict()})else:if target_location.location_id == target_person.location_id:return jsonify({'success':True,'message':f"User {userid} already enabled for Webex Calling in "f"location {args['location']}"})else:return jsonify({'success':False,'message':f"User {userid} already enabled for Webex Calling "f"but not in location {args['location']}"})
Save this file
In VS Code, start (or restart) your Flask app by clicking
Debug > Start Debugging (or
Restart Debugging, if already started).
The put method in the wbxc_user_disable_api class has already beeen implemented for you. This
function is used to disable Webex Calling and just does the opposite of the steps you just perfromed.
The function looks up the user details and the license, exactly as before, then removes the Webex Calling
License and adds the Unified Communication Manager (UCM) license. The only other difference is that since removing
the Webex Calling license also removes the phone number, you can simply save that data before
updating the user's license and then running another update to the user afterwards to re-apply the
phone number. You can look at the code in the wbxc_user_disable_api medthod if you want
to see the exact code that does this.
Click the GET request with the URI
"/wbxc/user/{userid} Retrieve a user in WbxC by user id"
Click Try it out at the right
In the userid field, enter the userid
pod9wbxuser
Click Execute
You should see a 200 response code and a successful message, with the entire user
object being printed out in the Response body.
In a previous section of this lab, you enabled Webex Calling features for the user pod9wbxuser. To test
the code you just implemented, you will first disable Webex Calling for this user, then re-enable it.
While still in the wbxc section of your
Portal API page,
click the PUT request with the URI "/wbxc/user/disable/{userid}"
and description "Disable a Webex Calling Person by user ID."
Click Try it out at the right
In the userid field, enter the userid
pod9wbxuser
Click Execute
You should see a 200 response code and a successful message, with a message
indicating Successfully disabled Webex Calling for user pod9wbxuser.
The Webex app should be already running on your PC. Within a few seconds of disabling Webex Calling,
you should see the bottom left corner of the Webex App show an error indicating "Phone services are disconnected".
Now go through the steps to re-enable Webex Calling on the phone using the API you created.
Remain in the wbxc section of your Portal API page
and find the PUT request with the URI
"/wbxc/user/enable/{userid}" and description "Enable a Person for Webex Calling by user ID".
Click Try it out at the right
Make sure the location field is set to your Pod number.
In the userid field, enter the userid
pod9wbxuser
Click Execute
You should see a 200 response code and a successful message, with the entire user
object being printed out in the Response body.
To get phone services back up and running quickly after re-enabling Webex calling, sign out of your Webex App, then sign in again with
pod9wbxuser@collab-api.com
and password:
C1sco.123
. You must sign out and sign back in
for the call to work.
Once signed in, you should see the Call Settings in the bottom left corner. There should be no
"Phone services are disconnected" or similar errors.
In the Webex app, click the PU icon at top left. Click Settings,
then click Phone Service. Make sure it shows Webex Calling
and connected.
Click the following to place a call: +19197091111.
This calls another UCM-registered phone via a CUBE Local gateway between Cisco Webex and your
pod's Unified CM.
Webex Calling has a vast amount of configuration exposed via the API. From placing calls to configuring
call handling, many things can be automated and simplified depending on your requirements. In the
next section, you will take a look at using a Bot to send notifications as well as receive commands
that can execute some of the same APIs that you have already implemented.
Note: You will need to have a valid access token for this to work.
import logging
import json
from flask import request, jsonify
from flask_restx import Namespace, Resource
from flaskr.api.v1.parsers import wbxc_enable_user_args
from os import getenv
from wxc_sdk.rest import RestError
from.service_app import ServiceApp
logging.basicConfig(level=logging.INFO,format='%(asctime)s %(threadName)s %(levelname)s %(name)s %(message)s')
logging.getLogger('urllib3.connectionpool').setLevel(logging.INFO)# Change to DEBUG for detailed REST interaction output
logging.getLogger('wxc_sdk.rest').setLevel(logging.INFO)
log = logging.getLogger(__name__)
api = Namespace('wbxc', description='Webex Calling APIs')defto_email(user_id):# Return the email address given the user idreturn user_id +'@'+ getenv('DOMAIN')defget_person_det(email):"""
Searches the Webex Persons by email address, if exactly one is found, look up and return
the Person details, including Webex Calling Data.
"""# Create a Service App instance
sa = ServiceApp()# Search all users and find the one containing the email
wxc_people_list =list(sa.api.people.list(email=email))iflen(wxc_people_list)==1:# Look up user details, including Webex Calling data; the wxc_people_list has exactly one entry at index[0]
my_person = sa.api.people.details(person_id=wxc_people_list[0].person_id, calling_data=True)return my_person
defget_lic_by_name(licenses, name):"""
Searches Webex licenses and returns the license if a matching "name" is found.
"""for lic in licenses:if lic.name == name:return lic
@api.route("/user/<string:userid>")@api.param('userid','The user id of the user to be returned')classwbxc_user_api(Resource):defget(self, userid):"""
Retrieve a user in WbxC by user id.
"""try:# Get the detailed user by email
target_person = get_person_det(to_email(userid))ifnot target_person:return jsonify({'success':False,'message':f'User not found with email {to_email(userid)}'})return jsonify({'success':True,'message':f'Successfully found user {userid}','user_data': target_person.dict()})except Exception as e:# Return any API error that may have been raisedreturn jsonify({'success':False,'message':str(e)})@api.route('/user/enable/<string:userid>')@api.param('userid','The user id of the user to be enabled')classwbxc_user_enable_api(Resource):@api.expect(wbxc_enable_user_args, validate=True)defput(self, userid):"""
Enable a Person for Webex Calling by user ID.
"""try:# Read all arguments
args = wbxc_enable_user_args.parse_args(request)# Get the person details for a user with this email address
target_person = get_person_det(to_email(userid))ifnot target_person:return jsonify({'success':False,'message':f'User not found with email {to_email(userid)}'})# Create a Service App instance
sa = ServiceApp()# Search all licenses and find the one matching the specified license
license_list =list(sa.api.licenses.list())
wxc_pro_license = get_lic_by_name(licenses=license_list, name='Webex Calling - Professional')
ucm_license = get_lic_by_name(licenses=license_list, name='Unified Communication Manager (UCM)')# Find the location provided location name
target_location = sa.api.locations.by_name(args['location'])ifnot target_location:return jsonify({'success':False,'message':f'Location not found: {args["location"]}'})# Look up the user's phone number (set in Active Directory and synched to the Webex work number)
target_tn =Nonefor num in target_person.phone_numbers:if num.number_type =='work':
target_tn = num.value
ifnot target_tn:return jsonify({'success':False,'message':f'Phone number not configured for user: {userid}'})# Check if user is already enabled for Webex Calling (i.e. they have a location_id assigned)ifnot target_person.location_id:# Append the Webex license and remove Unified Communication Manager (UCM), if present
target_person.licenses.append(wxc_pro_license.license_id)if ucm_license.license_id in target_person.licenses:
target_person.licenses.remove(ucm_license.license_id)# Set the user's location id
target_person.location_id = target_location.location_id
# Update the user
sa.api.people.update(person=target_person, calling_data=True)return jsonify({'success':True,'message':f"Successfully enabled Webex Calling for user {userid} on "f"location {args['location']} with number {target_tn}",'user_data': target_person.dict()})else:if target_location.location_id == target_person.location_id:return jsonify({'success':True,'message':f"User {userid} already enabled for Webex Calling in "f"location {args['location']}"})else:return jsonify({'success':False,'message':f"User {userid} already enabled for Webex Calling "f"but not in location {args['location']}"})# Return any REST error that may have been raisedexcept RestError as e:return jsonify({'success':False,'message':str(e)})@api.route('/user/disable/<string:userid>')@api.param('userid','The user id of the user to be disabled')classwbxc_user_disable_api(Resource):defput(self, userid):"""
Disable a Webex Calling Person by user ID.
"""try:# Get the person details for a user with this email address
target_person = get_person_det(to_email(userid))ifnot target_person:return jsonify({'success':False,'message':f'User not found with email {to_email(userid)}'})# Check if enabled for WxCif target_person.location_id:# Create a Service App instance
sa = ServiceApp()# Search all licenses and find the one matching the specified license
license_list =list(sa.api.licenses.list())
wxc_pro_license = get_lic_by_name(licenses=license_list, name='Webex Calling - Professional')
ucm_license = get_lic_by_name(licenses=license_list, name='Unified Communication Manager (UCM)')# Disable Webex Calling by removing the webex calling license (this will automatically clear the# location and the WxC phone number) and re-add the on-prem calling licenseif wxc_pro_license:
target_person.licenses.remove(wxc_pro_license.license_id)if ucm_license:if ucm_license.license_id notin target_person.licenses:
target_person.licenses.append(ucm_license.license_id)# Update the user, this removes the license and location
target_person = sa.api.people.update(person=target_person, calling_data=True)
result ={'success':True,'message':f'Successfully disabled Webex Calling for user {userid}','user_data': target_person.dict()})else:
result ={'success':False,'message':f'Cannot enable Webex Calling for {userid}. Webex Calling - Professional 'f'license not found.'}else:
result ={'success':True,'message':f'User {userid} already disabled for Webex Calling'}return jsonify(result)except RestError as e:# Return any REST error that may have been raisedreturn jsonify({'success':False,'message':str(e)})