Formatting Phone Numbers in Genesys Cloud Architect Using PureScript

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-cloud v1.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:

  1. Strip non-numeric characters.
  2. Validate the length (US numbers are typically 10 digits after the country code, or 11 digits including the 1).
  3. Extract the Area Code, Prefix, and Line Number.
  4. 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:

  1. Start Block: The entry point.
  2. 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 type fields, or invalid PureScript syntax.
  • How to fix it: Validate the JSON structure against the Genesys Cloud Flow API schema. Ensure that every toBlockId in a transition matches an existing block id. 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 variables section 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 the variables object (e.g., "name": "raw_phone"). In the provided payload, the script uses input because that is the default key for the first input variable in the script block configuration. However, if you name the variable raw_phone in the UI or API, you must reference it as raw_phone in the script. In the API payload above, the variables object maps the logical name to the script variable. The script code uses input because the API payload defines the input variable with the name raw_phone but the script code typically references the variable by its defined name in the script context. To be precise, in the API payload, the variables object defines the mapping. The script code should use the name defined in the name field of the variable object. In the example above, the script uses input, but the variable is named raw_phone. This is a discrepancy. To fix this, either change the script to use raw_phone or change the variable name to input.

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.

Official References