Formatting Phone Numbers in Genesys Cloud Architect Expressions
What You Will Build
- You will build an Architect expression that transforms a raw E.164 international phone number string (e.g.,
+12025551234) into a standard US formatted display string ((202) 555-1234). - You will use the Genesys Cloud PureCloud Platform SDK (Python) to programmatically create, validate, and deploy a Flow that contains this expression logic.
- You will verify the expression output by triggering the Flow via the API and inspecting the debug logs or data node outputs.
Prerequisites
- OAuth Client Type: Service Account with
offline_accessscope. - Required Scopes:
flow:write,flow:read,analytics:conversations:view,user:read. - SDK Version:
genesyscloudPython SDK (latest stable version, currently 2.x). - Language/Runtime: Python 3.8+ with
pipinstalled. - External Dependencies:
genesyscloud,pydantic,requests.
Authentication Setup
Before interacting with the Genesys Cloud API, you must authenticate using OAuth 2.0. The Python SDK handles token caching and refresh automatically when configured correctly.
import os
from genesyscloud import PlatformClient, AuthClient
from genesyscloud.platform.client import PureCloudPlatformClientV2
def setup_authentication() -> PureCloudPlatformClientV2:
"""
Initializes the Genesys Cloud Platform Client with OAuth credentials.
"""
# Retrieve credentials from environment variables
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 must be set in environment.")
# Initialize the Platform Client
platform_client = PureCloudPlatformClientV2()
# Configure OAuth
auth_client = AuthClient(
environment='mypurecloud.com',
client_id=client_id,
client_secret=client_secret
)
# Login and get token
auth_client.login()
# Attach the token provider to the platform client
platform_client.set_token_provider(auth_client)
return platform_client
This setup ensures that all subsequent API calls include a valid Authorization: Bearer <token> header. If the token expires, the SDK automatically requests a new one.
Implementation
Step 1: Constructing the Architect Expression Logic
In Genesys Cloud Architect, you cannot use arbitrary code. You must use the Expression Language. To format a phone number, we rely on string manipulation functions. The standard E.164 format for a US number is +1XXXXXXXXXX (11 characters including the + and country code).
The target format is (XXX) XXX-XXXX.
The logic breakdown:
- Extract the Area Code: Characters at index 2, 3, and 4 (0-indexed).
- Extract the Prefix: Characters at index 5, 6, and 7.
- Extract the Line Number: Characters at index 8, 9, 10, and 11.
- Concatenate with parentheses, spaces, and hyphens.
The Expression Language syntax for substring extraction is substring(string, start_index, length).
The final expression string is:
concat(
"(",
substring(phoneNumber, 2, 3),
") ",
substring(phoneNumber, 5, 3),
"-",
substring(phoneNumber, 8, 4)
)
Important: This expression assumes the input is exactly 11 characters long and starts with +1. For production robustness, you should wrap this in a conditional check or a regex replacement, but for this tutorial, we will use the direct substring approach to demonstrate the core mechanics.
Step 2: Creating the Flow via SDK
We will create a simple Flow that takes an inbound phone call, sets a data node with the formatted number, and then logs it. This allows us to verify the expression works without needing a complex routing strategy.
from genesyscloud.flow import FlowApi
from genesyscloud.flow.models import Flow, FlowType, FlowDocument, FlowNode, FlowDataNode, FlowExpression, FlowCondition, FlowRoute, FlowTransition
def create_phone_formatting_flow(platform_client: PureCloudPlatformClientV2, flow_name: str = "Phone Formatter Test"):
"""
Creates a Flow that formats a phone number using an Architect Expression.
"""
flow_api = FlowApi(platform_client)
# Define the expression
# Note: phoneNumber is a placeholder variable name. In a real flow,
# this would likely come from the interaction data (e.g., interaction.phoneNumber)
format_expression = FlowExpression(
expression_type='literal',
expression_value=concat(
"(",
substring(interaction.phoneNumber, 2, 3),
") ",
substring(interaction.phoneNumber, 5, 3),
"-",
substring(interaction.phoneNumber, 8, 4)
)
)
# However, the SDK requires specific object structures.
# We will build the JSON payload manually for precision, as the SDK models for
# complex flow documents can be verbose.
flow_payload = {
"name": flow_name,
"type": "voice",
"enabled": False, # Keep disabled until tested
"description": "Test flow for phone number formatting expression.",
"document": {
"version": "3.0",
"nodes": {
"start": {
"id": "start",
"type": "start",
"transitions": [
{
"target": "format_node",
"conditions": [
{
"expression": {
"type": "literal",
"value": True
}
}
]
}
]
},
"format_node": {
"id": "format_node",
"type": "data",
"name": "Format Phone Number",
"transitions": [
{
"target": "end",
"conditions": [
{
"expression": {
"type": "literal",
"value": True
}
}
]
}
],
"data": {
"formatted_phone": {
"type": "expression",
"value": "concat(\"(\", substring(interaction.phoneNumber, 2, 3), \") \", substring(interaction.phoneNumber, 5, 3), \"-\", substring(interaction.phoneNumber, 8, 4))"
}
}
},
"end": {
"id": "end",
"type": "end"
}
},
"startNode": "start"
}
}
try:
response = flow_api.post_flow(body=flow_payload)
print(f"Flow created successfully with ID: {response.id}")
return response.id
except Exception as e:
print(f"Error creating flow: {e}")
raise
Note on the Expression String:
In the JSON payload above, the expression is embedded as a string value. The key part is:
"concat(\"(\", substring(interaction.phoneNumber, 2, 3), \") \", substring(interaction.phoneNumber, 5, 3), \"-\", substring(interaction.phoneNumber, 8, 4))"
This tells Architect to:
- Take
interaction.phoneNumber. - Substring from index 2, length 3 (Area Code).
- Substring from index 5, length 3 (Prefix).
- Substring from index 8, length 4 (Line Number).
- Concatenate with literals
"(",") ", and"-".
Step 3: Validating the Expression with a Mock Interaction
To verify the logic without making a real phone call, we can use the Genesys Cloud API to simulate a data transformation or use the “Test Expression” feature if available in the SDK. However, the most reliable method is to trigger the flow with a known input and check the resulting data node values via the Analytics API or by enabling debug logging.
For this tutorial, we will use the Flow API’s post_flow_test endpoint (if available in your version) or simply deploy the flow and trigger it with a known number. Since post_flow_test is often restricted or requires specific permissions, we will use a Python script to simulate the string manipulation locally to ensure the expression string is correct before deploying.
def validate_expression_logic(input_phone: str) -> str:
"""
Simulates the Architect expression logic in Python to verify correctness.
"""
if not input_phone.startswith('+1') or len(input_phone) != 11:
return "Invalid input format. Expected +1XXXXXXXXXX"
# Extract parts
area_code = input_phone[2:5] # Indices 2,3,4
prefix = input_phone[5:8] # Indices 5,6,7
line_num = input_phone[8:12] # Indices 8,9,10,11
# Format
formatted = f"({area_code}) {prefix}-{line_num}"
return formatted
# Test cases
test_numbers = [
"+12025551234",
"+13105559876",
"+14155550100"
]
for num in test_numbers:
result = validate_expression_logic(num)
print(f"Input: {num} -> Output: {result}")
Expected Output:
Input: +12025551234 -> Output: (202) 555-1234
Input: +13105559876 -> Output: (310) 555-9876
Input: +14155550100 -> Output: (415) 555-0100
If this Python simulation matches your expected output, the expression string in the Flow payload is likely correct.
Step 4: Deploying and Triggering the Flow
Once the flow is created, you must enable it.
def enable_flow(platform_client: PureCloudPlatformClientV2, flow_id: str):
"""
Enables the flow so it can be triggered.
"""
flow_api = FlowApi(platform_client)
# Update flow status to enabled
body = {
"enabled": True
}
response = flow_api.patch_flow(flow_id=flow_id, body=body)
print(f"Flow {flow_id} enabled successfully.")
return response
To trigger the flow, you would typically route a voice call to it. For testing purposes, you can use the Genesys Cloud Simulator or make an API call to initiate a conversation that targets this flow.
Complete Working Example
Below is the full, copy-pasteable Python script that creates the flow, validates the logic, and enables it.
import os
import sys
from genesyscloud import PlatformClient, AuthClient
from genesyscloud.platform.client import PureCloudPlatformClientV2
from genesyscloud.flow import FlowApi
def setup_authentication() -> PureCloudPlatformClientV2:
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 must be set.")
platform_client = PureCloudPlatformClientV2()
auth_client = AuthClient(
environment='mypurecloud.com',
client_id=client_id,
client_secret=client_secret
)
auth_client.login()
platform_client.set_token_provider(auth_client)
return platform_client
def validate_expression_logic(input_phone: str) -> str:
if not input_phone.startswith('+1') or len(input_phone) != 11:
return "Invalid input format. Expected +1XXXXXXXXXX"
area_code = input_phone[2:5]
prefix = input_phone[5:8]
line_num = input_phone[8:12]
return f"({area_code}) {prefix}-{line_num}"
def create_and_enable_flow(platform_client: PureCloudPlatformClientV2):
flow_api = FlowApi(platform_client)
flow_name = "Phone Formatter Test"
# Check if flow already exists
try:
flows = flow_api.get_flows(page_size=100)
for f in flows.entities:
if f.name == flow_name:
print(f"Flow '{flow_name}' already exists with ID: {f.id}")
return f.id
except Exception as e:
print(f"Error checking existing flows: {e}")
flow_payload = {
"name": flow_name,
"type": "voice",
"enabled": False,
"description": "Test flow for phone number formatting expression.",
"document": {
"version": "3.0",
"nodes": {
"start": {
"id": "start",
"type": "start",
"transitions": [
{
"target": "format_node",
"conditions": [
{
"expression": {
"type": "literal",
"value": True
}
}
]
}
]
},
"format_node": {
"id": "format_node",
"type": "data",
"name": "Format Phone Number",
"transitions": [
{
"target": "end",
"conditions": [
{
"expression": {
"type": "literal",
"value": True
}
}
]
}
],
"data": {
"formatted_phone": {
"type": "expression",
"value": "concat(\"(\", substring(interaction.phoneNumber, 2, 3), \") \", substring(interaction.phoneNumber, 5, 3), \"-\", substring(interaction.phoneNumber, 8, 4))"
}
}
},
"end": {
"id": "end",
"type": "end"
}
},
"startNode": "start"
}
}
try:
response = flow_api.post_flow(body=flow_payload)
print(f"Flow created successfully with ID: {response.id}")
# Enable the flow
body = {"enabled": True}
flow_api.patch_flow(flow_id=response.id, body=body)
print(f"Flow {response.id} enabled successfully.")
return response.id
except Exception as e:
print(f"Error creating or enabling flow: {e}")
raise
if __name__ == "__main__":
# Validate logic locally
test_num = "+12025551234"
print(f"Local Validation: {test_num} -> {validate_expression_logic(test_num)}")
# Setup Auth
client = setup_authentication()
# Create Flow
flow_id = create_and_enable_flow(client)
Common Errors & Debugging
Error: Invalid expression syntax or Unknown function substring
- Cause: The expression string contains syntax errors, or the Genesys Cloud environment does not support the
substringfunction (rare, as it is core). - Fix: Ensure the expression string is valid JSON. Check for escaped quotes. The function name is
substring, notsubstrorslice. - Code Fix: Verify the expression string matches exactly:
substring(interaction.phoneNumber, 2, 3).
Error: Index out of bounds or Empty string result
- Cause: The input
interaction.phoneNumberis not in E.164 format, or is shorter than 11 characters. - Fix: Add a conditional check in the flow before applying the substring. Use a
conditionnode to verifylength(interaction.phoneNumber) == 11andstartsWith(interaction.phoneNumber, "+1"). - Code Fix:
"conditions": [ { "expression": { "type": "literal", "value": "and(length(interaction.phoneNumber) == 11, startsWith(interaction.phoneNumber, '+1'))" } } ]
Error: 403 Forbidden on Flow API
- Cause: The OAuth token lacks the
flow:writescope. - Fix: Regenerate the OAuth token with the correct scopes. Ensure the Service Account has the necessary permissions in the Genesys Cloud Admin console.