Formatting Phone Numbers in Genesys Architect: From E.164 to Standard US Format

Formatting Phone Numbers in Genesys Architect: From E.164 to Standard US Format

What You Will Build

  • One sentence: You will create a Genesys Cloud Architect flow that ingests a raw E.164 phone number string and transforms it into a standard North American format (XXX) XXX-XXXX.
  • One sentence: This uses the Genesys Cloud Architect API to define the flow logic and the Genesys Cloud Python SDK to deploy the configuration.
  • One sentence: The primary implementation logic uses Python for the deployment script and JSON for the Architect flow definition.

Prerequisites

  • OAuth Client Type: Private Key or Client Credentials grant.
  • Required Scopes: flow:write, flow:read, organization:write.
  • SDK Version: genesys-cloud-purecloud-apis v3.0.0 or later.
  • Language/Runtime: Python 3.9+.
  • External Dependencies: pip install genesys-cloud-purecloud-apis requests.
  • Architect Knowledge: Basic understanding of Architect blocks (Start, Set, End) and variable scopes.

Authentication Setup

Genesys Cloud API calls require a valid OAuth 2.0 bearer token. For automated deployment scripts, the Private Key grant type is recommended because it does not expire like user tokens.

The following Python snippet demonstrates how to initialize the SDK client with private key authentication. This client object will be reused for all subsequent API calls.

import os
import json
from datetime import datetime
from purecloudplatformclientv2 import (
    ApiClient,
    Configuration,
    FlowApi,
    FlowDocument,
    FlowDocumentVersion,
    FlowDocumentVersionRequest
)

def get_genesys_client():
    """
    Initializes the Genesys Cloud API client using Private Key authentication.
    """
    # Load credentials from environment variables
    environment = os.getenv("GENESYS_ENVIRONMENT", "mypurecloud.com")
    client_id = os.getenv("GENESYS_CLIENT_ID")
    private_key_path = os.getenv("GENESYS_PRIVATE_KEY_PATH")

    if not client_id or not private_key_path:
        raise ValueError("Missing required environment variables: GENESYS_CLIENT_ID, GENESYS_PRIVATE_KEY_PATH")

    # Read the private key
    with open(private_key_path, "r") as f:
        private_key_content = f.read()

    # Configure the SDK
    config = Configuration()
    config.host = f"https://api.{environment}"
    config.client_id = client_id
    config.private_key = private_key_content

    api_client = ApiClient(configuration=config)
    return api_client

# Initialize the client
client = get_genesys_client()
flow_api = FlowApi(client)

Implementation

Step 1: Define the Architect Flow Structure

Architect flows are defined as JSON documents. To format a phone number, we need three core blocks:

  1. Start: Defines the input variable containing the raw E.164 number.
  2. Set: Contains the logic to parse and reformat the string.
  3. End: Outputs the formatted result.

The core logic relies on Genesys Cloud’s built-in string manipulation functions. We will use substring to extract area code, prefix, and line number, and string concatenation to assemble the final format.

Assume the input variable is named raw_phone with value +14155552671.

Here is the JSON structure for the flow definition. Note the use of expression in the set block.

{
  "name": "Phone Number Formatter",
  "type": "flow",
  "description": "Converts E.164 +1XXXXXXXXXX to (XXX) XXX-XXXX",
  "state": "PUBLISHED",
  "settings": {
    "maxConcurrentCalls": 100
  },
  "blocks": {
    "start": {
      "type": "start",
      "settings": {
        "inputs": [
          {
            "name": "raw_phone",
            "type": "string",
            "description": "Input phone number in E.164 format (e.g., +14155552671)"
          }
        ]
      },
      "transitions": {
        "success": "format_phone"
      }
    },
    "format_phone": {
      "type": "set",
      "settings": {
        "sets": [
          {
            "name": "clean_phone",
            "value": "replace(raw_phone, '+1', '')"
          },
          {
            "name": "area_code",
            "value": "substring(clean_phone, 0, 3)"
          },
          {
            "name": "prefix",
            "value": "substring(clean_phone, 3, 3)"
          },
          {
            "name": "line_number",
            "value": "substring(clean_phone, 6, 4)"
          },
          {
            "name": "formatted_phone",
            "value": "concat('(', area_code, ') ', prefix, '-', line_number)"
          }
        ]
      },
      "transitions": {
        "success": "end_flow"
      }
    },
    "end_flow": {
      "type": "end",
      "settings": {
        "outputs": [
          {
            "name": "formatted_phone",
            "type": "string",
            "description": "The final formatted phone number"
          }
        ]
      }
    }
  }
}

Explanation of the Logic:

  • replace(raw_phone, '+1', ''): Removes the country code. This assumes the input is strictly North American. For production systems, you should validate the length of the string first.
  • substring(clean_phone, 0, 3): Extracts characters from index 0 with length 3 (Area Code).
  • substring(clean_phone, 3, 3): Extracts characters from index 3 with length 3 (Prefix).
  • substring(clean_phone, 6, 4): Extracts characters from index 6 with length 4 (Line Number).
  • concat(...): Assembles the parts with parentheses and hyphens.

Step 2: Create the Flow via API

Now that we have the JSON structure, we use the Python SDK to create the flow. This step uploads the flow definition to Genesys Cloud.

import json
import os

def create_phone_formatter_flow(flow_json_path: str) -> str:
    """
    Creates a new Architect flow from a JSON file.
    
    Args:
        flow_json_path: Path to the JSON file containing the flow definition.
        
    Returns:
        The ID of the newly created flow.
    """
    try:
        # Load the flow definition from JSON
        with open(flow_json_path, 'r') as f:
            flow_data = json.load(f)

        # Map the JSON data to the SDK object
        flow_document = FlowDocument(
            name=flow_data['name'],
            type=flow_data['type'],
            description=flow_data.get('description', ''),
            state=flow_data['state'],
            settings=flow_data.get('settings', {}),
            blocks=flow_data['blocks']
        )

        # Call the API to create the flow
        api_response = flow_api.post_flows(
            body=flow_document,
            async_req=False
        )

        print(f"Flow created successfully with ID: {api_response.id}")
        return api_response.id

    except Exception as e:
        print(f"Error creating flow: {e}")
        raise

# Usage
flow_id = create_phone_formatter_flow("phone_formatter.json")

Critical Parameter Notes:

  • state: Set to PUBLISHED to make the flow immediately usable. Use DRAFT if you plan to make further edits before activation.
  • blocks: The key names in this dictionary (start, format_phone, end_flow) become the internal identifiers for the blocks. They must be unique within the flow.

Step 3: Validate and Test the Flow

Before deploying to production traffic, you should verify that the flow executes correctly. Genesys Cloud provides a test endpoint that allows you to simulate an execution without consuming license resources.

def test_flow_execution(flow_id: str, input_phone: str) -> dict:
    """
    Tests the flow execution with sample input.
    
    Args:
        flow_id: The ID of the flow to test.
        input_phone: The raw phone number to test with.
        
    Returns:
        The output variables from the test execution.
    """
    try:
        # Prepare the test input
        test_input = {
            "raw_phone": input_phone
        }

        # Call the test endpoint
        # Note: The Python SDK may not have a direct 'test_flow' method exposed 
        # in all versions, so we often use the raw API call via the client.
        # However, if using the FlowApi directly:
        
        # For this example, we will use a direct HTTP request via the underlying 
        # requests library for clarity, as the SDK's test endpoint can vary by version.
        
        url = f"https://api.mypurecloud.com/api/v2/flows/{flow_id}/test"
        
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {client._configuration.get_access_token()}"
        }
        
        payload = {
            "inputs": test_input
        }
        
        response = client._session.post(url, json=payload, headers=headers)
        
        if response.status_code == 200:
            result = response.json()
            print("Test Execution Result:")
            print(json.dumps(result, indent=2))
            return result
        else:
            print(f"Test failed with status {response.status_code}: {response.text}")
            return {}

    except Exception as e:
        print(f"Error testing flow: {e}")
        raise

# Run the test
test_flow_execution(flow_id, "+14155552671")

Expected Response:

{
  "status": "success",
  "outputs": {
    "formatted_phone": "(415) 555-2671"
  }
}

Complete Working Example

Below is the complete, runnable Python script. It combines authentication, flow creation, and testing into a single module. Save this as deploy_phone_formatter.py.

import os
import json
import sys
from purecloudplatformclientv2 import (
    ApiClient,
    Configuration,
    FlowApi,
    FlowDocument
)

# --- Configuration ---
ENVIRONMENT = os.getenv("GENESYS_ENVIRONMENT", "mypurecloud.com")
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
PRIVATE_KEY_PATH = os.getenv("GENESYS_PRIVATE_KEY_PATH")

# --- Flow Definition ---
FLOW_DEFINITION = {
    "name": "Phone Number Formatter (US)",
    "type": "flow",
    "description": "Converts E.164 +1XXXXXXXXXX to (XXX) XXX-XXXX",
    "state": "DRAFT",
    "settings": {
        "maxConcurrentCalls": 50
    },
    "blocks": {
        "start": {
            "type": "start",
            "settings": {
                "inputs": [
                    {
                        "name": "raw_phone",
                        "type": "string",
                        "description": "Input phone number in E.164 format"
                    }
                ]
            },
            "transitions": {
                "success": "format_phone"
            }
        },
        "format_phone": {
            "type": "set",
            "settings": {
                "sets": [
                    {
                        "name": "clean_phone",
                        "value": "replace(raw_phone, '+1', '')"
                    },
                    {
                        "name": "area_code",
                        "value": "substring(clean_phone, 0, 3)"
                    },
                    {
                        "name": "prefix",
                        "value": "substring(clean_phone, 3, 3)"
                    },
                    {
                        "name": "line_number",
                        "value": "substring(clean_phone, 6, 4)"
                    },
                    {
                        "name": "formatted_phone",
                        "value": "concat('(', area_code, ') ', prefix, '-', line_number)"
                    }
                ]
            },
            "transitions": {
                "success": "end_flow"
            }
        },
        "end_flow": {
            "type": "end",
            "settings": {
                "outputs": [
                    {
                        "name": "formatted_phone",
                        "type": "string"
                    }
                ]
            }
        }
    }
}

def get_client():
    """Initialize the Genesys Cloud API client."""
    if not CLIENT_ID or not PRIVATE_KEY_PATH:
        raise ValueError("Set GENESYS_CLIENT_ID and GENESYS_PRIVATE_KEY_PATH environment variables.")

    with open(PRIVATE_KEY_PATH, "r") as f:
        private_key_content = f.read()

    config = Configuration()
    config.host = f"https://api.{ENVIRONMENT}"
    config.client_id = CLIENT_ID
    config.private_key = private_key_content

    return ApiClient(configuration=config)

def deploy_flow(client: ApiClient):
    """Create the flow in Genesys Cloud."""
    flow_api = FlowApi(client)
    
    flow_doc = FlowDocument(
        name=FLOW_DEFINITION['name'],
        type=FLOW_DEFINITION['type'],
        description=FLOW_DEFINITION['description'],
        state=FLOW_DEFINITION['state'],
        settings=FLOW_DEFINITION['settings'],
        blocks=FLOW_DEFINITION['blocks']
    )
    
    try:
        response = flow_api.post_flows(body=flow_doc)
        print(f"Flow deployed successfully. ID: {response.id}")
        return response.id
    except Exception as e:
        print(f"Failed to deploy flow: {e}")
        sys.exit(1)

def test_flow(client: ApiClient, flow_id: str):
    """Test the deployed flow."""
    url = f"https://api.{ENVIRONMENT}/api/v2/flows/{flow_id}/test"
    
    # Get the access token from the configured client
    token = client.configuration.get_access_token()
    
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }
    
    payload = {
        "inputs": {
            "raw_phone": "+14155552671"
        }
    }
    
    try:
        response = client._session.post(url, json=payload, headers=headers)
        response.raise_for_status()
        result = response.json()
        print("Test Result:")
        print(json.dumps(result, indent=2))
    except Exception as e:
        print(f"Test failed: {e}")

if __name__ == "__main__":
    # Step 1: Authenticate
    api_client = get_client()
    
    # Step 2: Deploy Flow
    flow_id = deploy_flow(api_client)
    
    # Step 3: Test Flow
    test_flow(api_client, flow_id)

Common Errors & Debugging

Error: 400 Bad Request - Invalid Block Transition

What causes it: A block references a transition name that does not exist in its transitions object, or a block is referenced that does not exist in the blocks dictionary.
How to fix it: Ensure every block has a transitions object with keys matching the type of the next block (e.g., success, error). Verify all block IDs in transitions match the keys in the blocks dictionary.
Code Fix:

// Incorrect
"transitions": {
  "next": "format_phone" 
}

// Correct
"transitions": {
  "success": "format_phone"
}

Error: 403 Forbidden - Insufficient Scopes

What causes it: The OAuth token used by the script does not have the flow:write scope.
How to fix it: Update your OAuth client in the Genesys Cloud Admin Console. Navigate to Admin > Security > OAuth Clients, select your client, and add flow:write to the scopes.
Code Fix: No code change is required. Regenerate the token after updating the client scopes.

Error: 422 Unprocessable Entity - Invalid Expression

What causes it: The value in a set block contains a syntax error or references a variable that is out of scope.
How to fix it: Check the substring indices. Ensure clean_phone is defined before it is used in substring. Ensure the variable names in concat match the name fields in the sets array.
Code Fix:

// Ensure order of operations is correct
"sets": [
  { "name": "clean_phone", "value": "replace(raw_phone, '+1', '')" },
  { "name": "area_code", "value": "substring(clean_phone, 0, 3)" }
]

Official References