Formatting Phone Numbers in Genesys Cloud Architect Using Python SDK
What You Will Build
- A Python script that creates a custom Architect flow step to transform an E.164 phone number string into a standard US format
(XXX) XXX-XXXX. - The solution uses the Genesys Cloud Platform Client SDK to deploy a Flow containing a
Set Variablestep with a specific expression. - The tutorial covers Python, utilizing the
genesyscloudSDK to programmatically define the logic.
Prerequisites
- OAuth Client: A Genesys Cloud OAuth Client with the scope
flow:writeandflow:read. - SDK Version:
genesyscloudPython SDK version 130.0.0 or higher. - Language/Runtime: Python 3.9 or higher.
- External Dependencies:
pip install genesyscloud requests
Authentication Setup
Authentication in Genesys Cloud relies on OAuth 2.0 Client Credentials flow. The SDK handles token acquisition and refreshing automatically when initialized correctly. You must provide your Client ID, Client Secret, and the environment host (e.g., us-east-1.mygenesys.com).
import os
from purecloudplatformclientv2 import (
PlatformClient,
FlowApi,
Flow,
FlowStep,
SetVariableStep,
VariableExpression,
Expression,
ExpressionType,
ExpressionFunction,
ExpressionValue,
ExpressionVariable,
ExpressionOperator
)
def get_platform_client() -> PlatformClient:
"""
Initializes and returns a configured PlatformClient instance.
"""
environment_host = os.getenv("GENESYS_ENVIRONMENT", "api.mypurecloud.com")
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
platform_client = PlatformClient()
platform_client.set_environment(environment_host)
platform_client.set_credentials(client_id, client_secret)
return platform_client
Implementation
Step 1: Define the Formatting Logic
The core task is to convert +1XXXXXXXXXX to (XXX) XXX-XXXX. In Genesys Cloud Architect, this is achieved using the Set Variable step. The expression engine supports standard string manipulation functions.
The most robust method for this specific transformation in the expression editor is using replace and substring functions, or a combination of format if available in your specific platform version. However, the most universally compatible approach in the current expression engine involves extracting the area code and the remaining digits.
We will construct an expression that:
- Strips the country code prefix (
+1). - Extracts the first 3 digits (Area Code).
- Extracts the next 3 digits (Exchange).
- Extracts the last 4 digits (Subscriber Number).
- Concatenates them with parentheses and hyphens.
The expression logic in Architect syntax looks like this:
concat("(", substring(input, 2, 3), ")", " ", substring(input, 5, 3), "-", substring(input, 8, 4))
Note: The substring function in Genesys expressions is typically 0-indexed and takes a start index and a length.
Let us verify the indices for +1234567890:
- Index 0:
+ - Index 1:
1 - Index 2:
2(Start of Area Code) - Index 3:
3 - Index 4:
4 - Index 5:
5(Start of Exchange) - Index 6:
6 - Index 7:
7 - Index 8:
8(Start of Subscriber) - Index 9:
9 - Index 10:
0
Therefore:
- Area Code:
substring(input, 2, 3)→ “234” - Exchange:
substring(input, 5, 3)→ “567” - Subscriber:
substring(input, 8, 4)→ “890”
Result: (234) 567-890
Step 2: Construct the Flow Step Object
We need to create the SetVariableStep object using the SDK. This requires defining the variable name, the type, and the expression.
def create_phone_formatting_step(variable_name: str = "formatted_phone") -> SetVariableStep:
"""
Creates a SetVariable step that formats an E.164 US phone number.
Args:
variable_name: The name of the output variable in the Architect flow.
Returns:
SetVariableStep: The configured step object.
"""
# Define the input variable reference.
# Assuming the input variable is named 'raw_phone' in the flow context.
input_var_ref = ExpressionVariable(
variable="raw_phone"
)
# Construct the expression parts using ExpressionFunction calls.
# We build the expression tree programmatically.
# Helper to create a substring function call
def create_substring(var_ref: ExpressionVariable, start: int, length: int) -> ExpressionFunction:
return ExpressionFunction(
function="substring",
arguments=[
var_ref,
ExpressionValue(value=str(start)),
ExpressionValue(value=str(length))
]
)
area_code_expr = create_substring(input_var_ref, 2, 3)
exchange_expr = create_substring(input_var_ref, 5, 3)
subscriber_expr = create_substring(input_var_ref, 8, 4)
# Construct the full concat expression
# concat("(", area_code, ")", " ", exchange, "-", subscriber)
full_expression = ExpressionFunction(
function="concat",
arguments=[
ExpressionValue(value="("),
area_code_expr,
ExpressionValue(value=")"),
ExpressionValue(value=" "),
exchange_expr,
ExpressionValue(value="-"),
subscriber_expr
]
)
# Create the VariableExpression object
var_expression = VariableExpression(
expression=full_expression
)
# Create the SetVariableStep
step = SetVariableStep(
step_type="setVariable",
variable_name=variable_name,
variable_type="string",
expression=var_expression,
comment="Formats +1XXXXXXXXXX to (XXX) XXX-XXXX"
)
return step
Step 3: Assemble and Deploy the Flow
Now we wrap this step in a complete Flow definition. A minimal flow requires a start step and an end step, connected by the logic step.
def build_phone_formatting_flow() -> Flow:
"""
Builds a complete Flow object with start, set variable, and end steps.
"""
flow_api = FlowApi(get_platform_client())
# 1. Create the Start Step
start_step = FlowStep(
id="start",
step_type="start",
comment="Start of the phone formatting flow"
)
# 2. Create the Formatting Step (from Step 2)
format_step = create_phone_formatting_step(variable_name="formatted_phone")
format_step.id = "format_phone"
# 3. Create the End Step
end_step = FlowStep(
id="end",
step_type="end",
comment="End of the flow"
)
# 4. Define Transitions
# Start -> Format
start_step.transitions = [
FlowStepTransition(
condition="true",
target_step_id="format_phone"
)
]
# Format -> End
format_step.transitions = [
FlowStepTransition(
condition="true",
target_step_id="end"
)
]
# 5. Assemble the Flow Object
flow = Flow(
name="Phone Number Formatter",
description="Converts E.164 US numbers to (XXX) XXX-XXXX format",
type="task", # Can be 'task', 'voice', or 'webchat'
active=True,
version=1,
steps=[start_step, format_step, end_step]
)
return flow
def deploy_flow(flow: Flow) -> None:
"""
Creates or updates the flow in Genesys Cloud.
"""
flow_api = FlowApi(get_platform_client())
try:
# Attempt to create the flow
response = flow_api.post_flow(body=flow)
print(f"Flow created successfully. ID: {response.id}")
# Note: To use the flow, you may need to publish it.
# flow_api.post_flow_publish(flow_id=response.id, body=PublishRequest())
except Exception as e:
# Handle 409 Conflict (Flow already exists) or other errors
if "409" in str(e):
print("Flow already exists. Consider updating instead.")
# In a real scenario, you would fetch the existing flow and update it.
else:
print(f"Error deploying flow: {e}")
Complete Working Example
Below is the full, copy-pasteable Python script. It imports all necessary modules, handles authentication, constructs the flow with the specific expression logic, and attempts to deploy it.
import os
import sys
from purecloudplatformclientv2 import (
PlatformClient,
FlowApi,
Flow,
FlowStep,
FlowStepTransition,
SetVariableStep,
VariableExpression,
Expression,
ExpressionType,
ExpressionFunction,
ExpressionValue,
ExpressionVariable,
PublishRequest
)
def get_platform_client() -> PlatformClient:
environment_host = os.getenv("GENESYS_ENVIRONMENT", "api.mypurecloud.com")
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
if not client_id or not client_secret:
raise ValueError("Environment variables GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET are required.")
platform_client = PlatformClient()
platform_client.set_environment(environment_host)
platform_client.set_credentials(client_id, client_secret)
return platform_client
def create_phone_formatting_step(variable_name: str = "formatted_phone") -> SetVariableStep:
# Reference the input variable 'raw_phone' which must exist in the flow context
# For a voice flow, this might be set by a 'Get Input' step or passed via API.
input_var_ref = ExpressionVariable(
variable="raw_phone"
)
# Helper to create substring function calls
def create_substring(var_ref: ExpressionVariable, start: int, length: int) -> ExpressionFunction:
return ExpressionFunction(
function="substring",
arguments=[
var_ref,
ExpressionValue(value=str(start)),
ExpressionValue(value=str(length))
]
)
# Indices for +1XXXXXXXXXX
# 0:+, 1:1, 2-4:Area, 5-7:Exchange, 8-11:Subscriber
area_code_expr = create_substring(input_var_ref, 2, 3)
exchange_expr = create_substring(input_var_ref, 5, 3)
subscriber_expr = create_substring(input_var_ref, 8, 4)
# Build the concat expression: (Area) Exchange-Subscriber
full_expression = ExpressionFunction(
function="concat",
arguments=[
ExpressionValue(value="("),
area_code_expr,
ExpressionValue(value=")"),
ExpressionValue(value=" "),
exchange_expr,
ExpressionValue(value="-"),
subscriber_expr
]
)
var_expression = VariableExpression(
expression=full_expression
)
step = SetVariableStep(
step_type="setVariable",
variable_name=variable_name,
variable_type="string",
expression=var_expression,
comment="Formats +1XXXXXXXXXX to (XXX) XXX-XXXX"
)
step.id = "format_phone"
return step
def build_and_deploy_flow() -> None:
platform_client = get_platform_client()
flow_api = FlowApi(platform_client)
# 1. Start Step
start_step = FlowStep(
id="start",
step_type="start",
comment="Start"
)
start_step.transitions = [
FlowStepTransition(
condition="true",
target_step_id="format_phone"
)
]
# 2. Format Step
format_step = create_phone_formatting_step(variable_name="formatted_phone")
# 3. End Step
end_step = FlowStep(
id="end",
step_type="end",
comment="End"
)
# Set transition from format to end
format_step.transitions = [
FlowStepTransition(
condition="true",
target_step_id="end"
)
]
# 4. Flow Definition
flow = Flow(
name="DevAdv_PhoneFormatter",
description="Formats E.164 US phone numbers using Python SDK",
type="task",
active=True,
version=1,
steps=[start_step, format_step, end_step]
)
try:
print("Deploying flow...")
response = flow_api.post_flow(body=flow)
print(f"Success: Flow created with ID {response.id}")
# Optional: Publish the flow immediately
# publish_req = PublishRequest()
# flow_api.post_flow_publish(flow_id=response.id, body=publish_req)
# print(f"Flow published.")
except Exception as e:
error_msg = str(e)
if "409" in error_msg:
print("Flow with this name already exists. Check your Genesys Cloud org.")
elif "401" in error_msg or "403" in error_msg:
print("Authentication/Authorization error. Check Client ID, Secret, and Scopes.")
else:
print(f"Error: {error_msg}")
sys.exit(1)
if __name__ == "__main__":
build_and_deploy_flow()
Common Errors & Debugging
Error: 400 Bad Request - Invalid Expression
What causes it: The ExpressionFunction arguments are malformed. The Genesys Cloud expression engine is strict about types. If you pass an integer where a string is expected, or vice versa, the API will reject the flow creation.
How to fix it: Ensure all literal values in ExpressionValue are passed as strings if the function expects strings, or verify the function signature. In the substring function, the start and length parameters are integers, but the SDK often serializes them correctly if passed as numbers. However, if you encounter issues, explicitly casting to string in the ExpressionValue constructor is safer for literal constants.
Code Fix:
In the create_substring helper, ensure ExpressionValue(value=str(start)) is used. The SDK expects JSON-serializable values.
Error: 409 Conflict - Flow Already Exists
What causes it: You attempted to create a flow with a name that is already taken in your Genesys Cloud organization. Flow names must be unique within the organization.
How to fix it: Either delete the existing flow, rename the new flow in the Flow object constructor, or implement an update logic that fetches the existing flow ID and uses patch_flow instead of post_flow.
Code Fix:
Modify the build_and_deploy_flow function to check for existence first or handle the 409 response gracefully by prompting for a unique name.
Error: 401 Unauthorized - Missing Scope
What causes it: The OAuth client used does not have the flow:write scope.
How to fix it: Go to Genesys Cloud Admin → Security → OAuth Clients. Select your client and add flow:write and flow:read to the scopes. Re-generate the client secret if necessary.
Edge Case: Non-US Numbers
What causes it: The expression assumes a 11-digit US number starting with +1. If the input is +44... (UK) or has a different length, the substring indices will return incorrect data or empty strings.
How to fix it: Add a validation step before the formatting step. Use an If step to check the length and prefix of the raw_phone variable.
# Example logic for an If step to validate length
validation_step = FlowStep(
id="validate_length",
step_type="if",
condition=ExpressionFunction(
function="equals",
arguments=[
ExpressionFunction(
function="length",
arguments=[ExpressionVariable(variable="raw_phone")]
),
ExpressionValue(value="11")
]
),
transitions=[
FlowStepTransition(condition="true", target_step_id="format_phone"),
FlowStepTransition(condition="false", target_step_id="error_handling")
]
)