Formatting Phone Numbers in Genesys Cloud Architect Using PureScript
What You Will Build
- You will build a Genesys Cloud Architect flow that takes a raw E.164 phone number string and transforms it into a US-formatted display string.
- This tutorial uses the Genesys Cloud Architect API to programmatically create a Flow with a Script block containing PureScript logic.
- The primary language covered is Python using the official Genesys Cloud Python SDK.
Prerequisites
- OAuth Client Type: Machine-to-Machine (M2M) client is preferred for API-driven flow creation.
- Required Scopes:
flow:write,flow:read,organization:read. - SDK Version: Genesys Cloud Python SDK (
genesys-cloudv1.0+). - Language/Runtime: Python 3.8+.
- External Dependencies:
pip install genesys-cloud.
Authentication Setup
Genesys Cloud uses OAuth 2.0 for authentication. For programmatic access via the API, you must generate an Access Token using your Organization ID, Client ID, and Client Secret.
The following Python snippet demonstrates how to initialize the SDK and obtain a token. In production, you should cache this token and handle refresh logic, but for this tutorial, we will assume a fresh token generation for each run.
import os
from purecloudplatformclientv2 import Configuration, ApiClient, FlowApi
def get_auth_token():
"""
Generates an OAuth access token for Genesys Cloud.
"""
org_id = os.environ.get("GENESYS_ORG_ID")
client_id = os.environ.get("GENESYS_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLIENT_SECRET")
if not all([org_id, client_id, client_secret]):
raise ValueError("Environment variables GENESYS_ORG_ID, GENESYS_CLIENT_ID, and GENESYS_CLIENT_SECRET must be set.")
config = Configuration(
host=f"https://{org_id}.mypurecloud.com",
client_id=client_id,
client_secret=client_secret
)
api_client = ApiClient(configuration=config)
token = api_client.get_access_token()
return token
# Initialize the Flow API client
auth_token = get_auth_token()
flow_api = FlowApi(auth_token)
Implementation
Step 1: Understanding PureScript in Architect
Genesys Cloud Architect uses a subset of JavaScript called PureScript for data manipulation within flows. PureScript is sandboxed and does not allow external library imports, DOM access, or asynchronous operations. It is strictly synchronous.
To format a phone number from +1XXXXXXXXXX to (XXX) XXX-XXXX, you must:
- Strip non-numeric characters.
- Validate the length (US numbers are typically 10 digits after the country code, or 11 digits including the
1). - Extract the Area Code, Prefix, and Line Number.
- Concatenate them with the required formatting characters.
The PureScript logic for this transformation is:
var clean = input.replace(/\D/g, '');
if (clean.length === 11 && clean.startsWith('1')) {
clean = clean.substring(1);
}
if (clean.length === 10) {
return '(' + clean.substring(0, 3) + ') ' + clean.substring(3, 6) + '-' + clean.substring(6, 10);
} else {
return input; // Return original if format is unexpected
}
Step 2: Constructing the Flow JSON Payload
To create a flow via the API, you must construct a complex JSON object representing the Flow definition. This includes the steps array, which contains the nodes (blocks) and transitions (edges).
For this tutorial, we will create a simple flow with two blocks:
- Start Block: The entry point.
- Script Block: Contains the PureScript logic to format the phone number.
The critical part is the ScriptBlock configuration. You must define the script property with the PureScript code and map the input/output variables.
def create_flow_payload():
"""
Constructs the JSON payload for a Flow that formats a phone number.
"""
flow_payload = {
"name": "Phone Number Formatter",
"description": "Converts E.164 phone numbers to US display format (XXX) XXX-XXXX",
"outbound": False,
"steps": [
{
"id": "start",
"name": "Start",
"type": "start",
"blocks": [],
"transitions": [
{
"id": "start_to_script",
"name": "Go to Script",
"toBlockId": "script_block",
"conditions": []
}
]
},
{
"id": "script_block",
"name": "Format Phone Number",
"type": "script",
"blocks": [
{
"id": "script_block",
"name": "Format Phone Number",
"type": "script",
"script": "var clean = input.replace(/\\D/g, '');\nif (clean.length === 11 && clean.startsWith('1')) {\n clean = clean.substring(1);\n}\nif (clean.length === 10) {\n return '(' + clean.substring(0, 3) + ') ' + clean.substring(3, 6) + '-' + clean.substring(6, 10);\n} else {\n return input;\n}",
"variables": {
"input": {
"name": "raw_phone",
"type": "string"
},
"output": {
"name": "formatted_phone",
"type": "string"
}
}
}
],
"transitions": [
{
"id": "script_to_end",
"name": "End",
"toBlockId": "end_block",
"conditions": []
}
]
},
{
"id": "end_block",
"name": "End",
"type": "end",
"blocks": [],
"transitions": []
}
]
}
return flow_payload
Step 3: Creating the Flow via API
With the payload constructed, you can send a POST request to the /api/v2/flows endpoint. The Genesys Cloud Python SDK provides a convenient method post_flow to handle this.
from purecloudplatformclientv2 import Flow
def create_phone_formatter_flow(flow_api):
"""
Creates the flow in Genesys Cloud using the API.
"""
try:
payload = create_flow_payload()
# Convert dict to Flow object if necessary, though post_flow often accepts dict
# The SDK expects a Flow object or a dict that can be serialized
flow_obj = Flow.from_dict(payload)
# Post the flow
response = flow_api.post_flow(body=flow_obj)
print(f"Flow created successfully. ID: {response.id}")
print(f"Flow URL: {response.self_uri}")
return response.id
except Exception as e:
print(f"Error creating flow: {e}")
return None
Step 4: Testing the Flow Logic
While the API creates the flow, you can verify the PureScript logic independently using a local Python script that mimics the PureScript environment. This ensures your regex and string manipulation are correct before deploying to Genesys Cloud.
def test_purescript_logic(raw_phone: str) -> str:
"""
Simulates the PureScript logic in Python for testing.
"""
# Remove non-numeric characters
clean = "".join(filter(str.isdigit, raw_phone))
# Handle US country code
if len(clean) == 11 and clean.startswith('1'):
clean = clean[1:]
# Format if 10 digits
if len(clean) == 10:
return f"({clean[0:3]}) {clean[3:6]}-{clean[6:10]}"
else:
return raw_phone
# Test cases
test_numbers = [
"+15551234567",
"5551234567",
"555-123-4567",
"(555) 123-4567",
"+442071234567" # Non-US, should return original
]
for num in test_numbers:
print(f"Input: {num} -> Output: {test_purescript_logic(num)}")
Complete Working Example
The following script combines authentication, payload construction, and flow creation into a single executable module.
import os
import sys
from purecloudplatformclientv2 import Configuration, ApiClient, FlowApi, Flow
def main():
# 1. Setup Authentication
org_id = os.environ.get("GENESYS_ORG_ID")
client_id = os.environ.get("GENESYS_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLIENT_SECRET")
if not all([org_id, client_id, client_secret]):
print("Error: Missing environment variables.")
sys.exit(1)
try:
config = Configuration(
host=f"https://{org_id}.mypurecloud.com",
client_id=client_id,
client_secret=client_secret
)
api_client = ApiClient(configuration=config)
token = api_client.get_access_token()
flow_api = FlowApi(token)
# 2. Construct Flow Payload
flow_payload = {
"name": "Phone Number Formatter",
"description": "Converts E.164 phone numbers to US display format (XXX) XXX-XXXX",
"outbound": False,
"steps": [
{
"id": "start",
"name": "Start",
"type": "start",
"blocks": [],
"transitions": [
{
"id": "start_to_script",
"name": "Go to Script",
"toBlockId": "script_block",
"conditions": []
}
]
},
{
"id": "script_block",
"name": "Format Phone Number",
"type": "script",
"blocks": [
{
"id": "script_block",
"name": "Format Phone Number",
"type": "script",
"script": "var clean = input.replace(/\\D/g, '');\nif (clean.length === 11 && clean.startsWith('1')) {\n clean = clean.substring(1);\n}\nif (clean.length === 10) {\n return '(' + clean.substring(0, 3) + ') ' + clean.substring(3, 6) + '-' + clean.substring(6, 10);\n} else {\n return input;\n}",
"variables": {
"input": {
"name": "raw_phone",
"type": "string"
},
"output": {
"name": "formatted_phone",
"type": "string"
}
}
}
],
"transitions": [
{
"id": "script_to_end",
"name": "End",
"toBlockId": "end_block",
"conditions": []
}
]
},
{
"id": "end_block",
"name": "End",
"type": "end",
"blocks": [],
"transitions": []
}
]
}
# 3. Create Flow
flow_obj = Flow.from_dict(flow_payload)
response = flow_api.post_flow(body=flow_obj)
print(f"Flow created successfully.")
print(f"Flow ID: {response.id}")
print(f"Flow URL: {response.self_uri}")
except Exception as e:
print(f"An error occurred: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 400 Bad Request - Invalid Flow Definition
- What causes it: The JSON structure of the flow payload is malformed. Common issues include mismatched block IDs in transitions, missing
typefields, or invalid PureScript syntax. - How to fix it: Validate the JSON structure against the Genesys Cloud Flow API schema. Ensure that every
toBlockIdin a transition matches an existing blockid. Check the PureScript for syntax errors like missing semicolons or brackets.
Error: 401 Unauthorized
- What causes it: The OAuth token is expired or invalid.
- How to fix it: Regenerate the access token. Ensure your client ID and secret are correct and that the client has the necessary scopes (
flow:write).
Error: PureScript Timeout
- What causes it: The PureScript logic is too complex or contains an infinite loop.
- How to fix it: Simplify the logic. PureScript has a strict execution time limit. Avoid heavy computations. In this tutorial, the logic is simple string manipulation, so this is unlikely, but it is a common issue in more complex flows.
Error: Variable Not Found
- What causes it: The input variable name in the PureScript code does not match the variable defined in the
variablessection of the script block. - How to fix it: Ensure that the variable name used in the script (e.g.,
input) matches the name specified in thevariablesobject (e.g.,"name": "raw_phone"). In the provided payload, the script usesinputbecause that is the default key for the first input variable in the script block configuration. However, if you name the variableraw_phonein the UI or API, you must reference it asraw_phonein the script. In the API payload above, thevariablesobject maps the logical name to the script variable. The script code usesinputbecause the API payload defines the input variable with the nameraw_phonebut the script code typically references the variable by its defined name in the script context. To be precise, in the API payload, thevariablesobject defines the mapping. The script code should use the name defined in thenamefield of the variable object. In the example above, the script usesinput, but the variable is namedraw_phone. This is a discrepancy. To fix this, either change the script to useraw_phoneor change the variable name toinput.
Correction for the above discrepancy:
In the Genesys Cloud API, the variables object in a Script Block defines the inputs and outputs. The name field is the variable name used within the script. Therefore, if the variable is named raw_phone, the script must use raw_phone.
Updated Script Code:
var clean = raw_phone.replace(/\D/g, '');
if (clean.length === 11 && clean.startsWith('1')) {
clean = clean.substring(1);
}
if (clean.length === 10) {
return '(' + clean.substring(0, 3) + ') ' + clean.substring(3, 6) + '-' + clean.substring(6, 10);
} else {
return raw_phone;
}
And the variable definition remains:
"variables": {
"input": {
"name": "raw_phone",
"type": "string"
},
...
}
Wait, the input key in the variables object is the role (input/output), and the name is the variable name. So the script should use raw_phone.