Increase Genesys Cloud Data Action Timeout via API

Increase Genesys Cloud Data Action Timeout via API

What You Will Build

  • A script that identifies Data Actions with insufficient timeouts and updates them to handle longer-running integrations.
  • This uses the Genesys Cloud PureCloud Platform Client V2 API.
  • The tutorial covers Python and JavaScript implementations.

Prerequisites

  • OAuth Client Type: Private Key Authentication (PKCE or Client Credentials are also supported, but Private Key is standard for server-side integrations).
  • Required Scopes:
    • dataactions:read
    • dataactions:write
  • SDK Version:
    • Python: genesys-cloud-sdk-python >= 170.0.0
    • JavaScript: @genesyscloud/purecloud-platform-client-v2 >= 170.0.0
  • Runtime Requirements:
    • Python 3.8+
    • Node.js 16+
  • External Dependencies:
    • Python: pip install genesys-cloud-sdk-python
    • JavaScript: npm install @genesyscloud/purecloud-platform-client-v2

Authentication Setup

Genesys Cloud APIs require a valid OAuth 2.0 access token. For backend scripts that modify configuration, use Private Key authentication. This method does not require user interaction and allows for long-running token validity.

Python Authentication

import os
from purecloud_platform_client import Configuration, PlatformClient, PrivateKeyAuthenticator

def get_platform_client() -> PlatformClient:
    """
    Initializes and returns an authenticated Genesys Cloud PlatformClient.
    """
    config = Configuration()
    
    # Load credentials from environment variables
    # GENESYS_ORG_ID, GENESYS_CLIENT_ID, GENESYS_PRIVATE_KEY_FILE_PATH
    config.set_property("org_id", os.getenv("GENESYS_ORG_ID"))
    config.set_property("client_id", os.getenv("GENESYS_CLIENT_ID"))
    config.set_property("private_key_file_path", os.getenv("GENESYS_PRIVATE_KEY_FILE_PATH"))
    
    # Optional: Set region if not default (us-east-1)
    # config.set_property("region", "eu-west-1")

    # Initialize the authenticator
    authenticator = PrivateKeyAuthenticator(config)
    
    # Create the platform client
    client = PlatformClient(authenticator)
    
    # Verify connection by fetching a simple resource
    try:
        # This is a quick check to ensure auth works before heavy operations
        client.get_user_me()
    except Exception as e:
        raise Exception(f"Authentication failed: {e}")
        
    return client

JavaScript Authentication

const { PlatformClient, Configuration, PrivateKeyAuthenticator } = require('@genesyscloud/purecloud-platform-client-v2');
const fs = require('fs');

async function getPlatformClient() {
    const config = new Configuration();
    
    config.set_property('org_id', process.env.GENESYS_ORG_ID);
    config.set_property('client_id', process.env.GENESYS_CLIENT_ID);
    // The SDK expects the content of the PEM file for JS, or a path depending on version.
    // For modern SDKs, passing the PEM content string is often more reliable in scripts.
    const privateKey = fs.readFileSync(process.env.GENESYS_PRIVATE_KEY_PATH, 'utf8');
    config.set_property('private_key', privateKey);

    const authenticator = new PrivateKeyAuthenticator(config);
    const client = new PlatformClient(authenticator);

    try {
        // Verify connection
        await client.getUserMe();
    } catch (error) {
        throw new Error(`Authentication failed: ${error.message}`);
    }

    return client;
}

Implementation

Step 1: Identify Data Actions with Low Timeouts

The default timeout for a Data Action in Genesys Cloud is often 3 seconds (3000 ms). If your integration calls an external API that takes 5 seconds, the Data Action will fail with a timeout error before your external API responds. You must first locate these actions.

The API endpoint is GET /api/v2/dataactions.

OAuth Scope: dataactions:read

Python Implementation

from purecloud_platform_client.rest import ApiException

def find_slow_data_actions(client: PlatformClient) -> list:
    """
    Retrieves all Data Actions and filters for those with timeout <= 3000ms.
    """
    data_action_api = client.data_actions_api
    actions_to_update = []
    
    try:
        # Fetch all data actions. The SDK handles pagination automatically if configured,
        # but for simplicity, we fetch the first page. 
        # In production, iterate through 'next_page' if exists.
        response = data_action_api.get_data_actions(page_size=100)
        
        if not response.entities:
            print("No Data Actions found.")
            return []

        for action in response.entities:
            # Check if timeout is set and is less than or equal to 3 seconds (3000ms)
            # Note: timeout is in milliseconds in the API model
            if action.timeout and action.timeout <= 3000:
                print(f"Found slow action: {action.name} (ID: {action.id}, Timeout: {action.timeout}ms)")
                actions_to_update.append(action)
                
    except ApiException as e:
        if e.status == 401:
            print("Authentication error: Token may be expired or invalid.")
        elif e.status == 403:
            print("Permission denied: Ensure 'dataactions:read' scope is granted.")
        else:
            print(f"API Error: {e}")
            
    return actions_to_update

JavaScript Implementation

async function findSlowDataActions(client) {
    const dataActionsApi = client.dataActionsApi;
    const actionsToUpdate = [];

    try {
        // Fetch data actions
        const response = await dataActionsApi.getDataActions({ pageSize: 100 });

        if (!response.entities || response.entities.length === 0) {
            console.log("No Data Actions found.");
            return [];
        }

        for (const action of response.entities) {
            // Check if timeout is set and is less than or equal to 3 seconds (3000ms)
            if (action.timeout !== undefined && action.timeout <= 3000) {
                console.log(`Found slow action: ${action.name} (ID: ${action.id}, Timeout: ${action.timeout}ms)`);
                actionsToUpdate.push(action);
            }
        }
    } catch (error) {
        if (error.status === 401) {
            console.error("Authentication error: Token may be expired or invalid.");
        } else if (error.status === 403) {
            console.error("Permission denied: Ensure 'dataactions:read' scope is granted.");
        } else {
            console.error(`API Error: ${error.message}`);
        }
    }

    return actionsToUpdate;
}

Step 2: Increase the Timeout Limit

Once identified, you must update the Data Action entity. The maximum timeout allowed for a Data Action is 10 seconds (10000 ms). You cannot set it higher than this via the API. If your external call takes longer than 10 seconds, you must redesign the architecture (e.g., use async webhooks or Genesys Cloud Task Router).

The API endpoint is PUT /api/v2/dataactions/{dataActionId}.

OAuth Scope: dataactions:write

Python Implementation

def update_data_action_timeout(client: PlatformClient, action_id: str, new_timeout_ms: int) -> bool:
    """
    Updates the timeout for a specific Data Action.
    """
    data_action_api = client.data_actions_api
    
    # 1. Fetch the current action to preserve existing configuration
    try:
        current_action = data_action_api.get_data_action_by_id(data_action_id)
    except ApiException as e:
        print(f"Failed to fetch action {data_action_id}: {e}")
        return False

    # 2. Validate the new timeout
    if new_timeout_ms > 10000:
        print(f"Error: Timeout cannot exceed 10000ms. Received {new_timeout_ms}ms.")
        return False
        
    if new_timeout_ms <= current_action.timeout:
        print(f"Skipping {action_id}: New timeout ({new_timeout_ms}) is not greater than current ({current_action.timeout}).")
        return False

    # 3. Update the timeout field
    # The SDK model allows direct attribute modification
    current_action.timeout = new_timeout_ms
    
    # 4. Send the update
    try:
        # The PUT request requires the full entity body
        data_action_api.put_data_action(data_action_id, current_action)
        print(f"Successfully updated {current_action.name} to {new_timeout_ms}ms.")
        return True
    except ApiException as e:
        if e.status == 409:
            # Conflict usually means version mismatch or invalid state
            print(f"Conflict updating {data_action_id}: {e.body}")
        elif e.status == 422:
            # Unprocessable Entity often indicates validation errors (e.g., invalid JSON schema)
            print(f"Validation error updating {data_action_id}: {e.body}")
        else:
            print(f"API Error updating {data_action_id}: {e}")
        return False

JavaScript Implementation

async function updateDataActionTimeout(client, actionId, newTimeoutMs) {
    const dataActionsApi = client.dataActionsApi;

    // 1. Fetch the current action to preserve existing configuration
    let currentAction;
    try {
        const fetchResponse = await dataActionsApi.getDataActionById(actionId);
        currentAction = fetchResponse.body;
    } catch (error) {
        console.error(`Failed to fetch action ${actionId}: ${error.message}`);
        return false;
    }

    // 2. Validate the new timeout
    if (newTimeoutMs > 10000) {
        console.error(`Error: Timeout cannot exceed 10000ms. Received ${newTimeoutMs}ms.`);
        return false;
    }

    if (newTimeoutMs <= currentAction.timeout) {
        console.log(`Skipping ${actionId}: New timeout (${newTimeoutMs}) is not greater than current (${currentAction.timeout}).`);
        return false;
    }

    // 3. Update the timeout field
    currentAction.timeout = newTimeoutMs;

    // 4. Send the update
    try {
        // The PUT request requires the full entity body
        await dataActionsApi.putDataAction(actionId, currentAction);
        console.log(`Successfully updated ${currentAction.name} to ${newTimeoutMs}ms.`);
        return true;
    } catch (error) {
        if (error.status === 409) {
            console.error(`Conflict updating ${actionId}: ${error.body}`);
        } else if (error.status === 422) {
            console.error(`Validation error updating ${actionId}: ${error.body}`);
        } else {
            console.error(`API Error updating ${actionId}: ${error.message}`);
        }
        return false;
    }
}

Step 3: Processing Results and Handling Pagination

In production environments, you may have hundreds of Data Actions. The GET /api/v2/dataactions endpoint supports pagination. You must iterate through all pages to ensure no slow action is missed.

Python Pagination Logic

def fetch_all_data_actions(client: PlatformClient) -> list:
    """
    Fetches all Data Actions handling pagination.
    """
    data_action_api = client.data_actions_api
    all_actions = []
    page_size = 100
    page_number = 1
    
    while True:
        try:
            response = data_action_api.get_data_actions(
                page_size=page_size,
                page_number=page_number
            )
            
            if not response.entities:
                break
                
            all_actions.extend(response.entities)
            
            # Check if there are more pages
            # The response object contains 'next_page' or we can infer from total
            if not hasattr(response, 'next_page') or not response.next_page:
                break
                
            page_number += 1
            
        except ApiException as e:
            print(f"Error fetching page {page_number}: {e}")
            break
            
    return all_actions

Complete Working Example

This Python script combines all steps: authenticates, finds Data Actions with timeouts <= 3000ms, and updates them to 10000ms (the maximum allowed).

import os
import sys
from purecloud_platform_client import Configuration, PlatformClient, PrivateKeyAuthenticator
from purecloud_platform_client.rest import ApiException

def init_client() -> PlatformClient:
    config = Configuration()
    config.set_property("org_id", os.getenv("GENESYS_ORG_ID"))
    config.set_property("client_id", os.getenv("GENESYS_CLIENT_ID"))
    config.set_property("private_key_file_path", os.getenv("GENESYS_PRIVATE_KEY_FILE_PATH"))
    
    # Optional: Specify region if not us-east-1
    # config.set_property("region", "eu-west-1")

    authenticator = PrivateKeyAuthenticator(config)
    client = PlatformClient(authenticator)
    
    # Verify Auth
    try:
        client.get_user_me()
    except Exception as e:
        raise Exception(f"Authentication failed: {e}")
        
    return client

def main():
    # 1. Initialize Client
    print("Initializing Genesys Cloud Client...")
    try:
        client = init_client()
    except Exception as e:
        print(e)
        sys.exit(1)

    # 2. Fetch All Data Actions
    print("Fetching Data Actions...")
    data_action_api = client.data_actions_api
    all_actions = []
    page_size = 100
    page_number = 1
    
    while True:
        try:
            response = data_action_api.get_data_actions(page_size=page_size, page_number=page_number)
            if not response.entities:
                break
            all_actions.extend(response.entities)
            
            if not hasattr(response, 'next_page') or not response.next_page:
                break
            page_number += 1
        except ApiException as e:
            print(f"Error fetching actions: {e}")
            break

    if not all_actions:
        print("No Data Actions found.")
        return

    print(f"Found {len(all_actions)} Data Actions.")

    # 3. Filter and Update
    target_timeout = 10000  # 10 seconds (Max allowed)
    current_threshold = 3000 # 3 seconds
    
    actions_to_update = [
        action for action in all_actions 
        if action.timeout is not None and action.timeout <= current_threshold
    ]

    print(f"Identified {len(actions_to_update)} actions with timeout <= {current_threshold}ms.")

    for action in actions_to_update:
        print(f"Processing: {action.name} (ID: {action.id}, Current: {action.timeout}ms)")
        
        # Validate max limit
        if target_timeout > 10000:
            print("Error: Target timeout exceeds maximum allowed (10000ms).")
            break

        try:
            # Update the timeout
            action.timeout = target_timeout
            
            # PUT request
            data_action_api.put_data_action(action.id, action)
            
            print(f"  -> Updated {action.name} to {target_timeout}ms.")
            
        except ApiException as e:
            print(f"  -> Failed to update {action.name}: {e.status} {e.reason}")
            # Handle specific errors
            if e.status == 429:
                print("  -> Rate limited. Implement exponential backoff in production.")
            elif e.status == 409:
                print("  -> Conflict. The action may have been modified by another user.")

    print("Update process complete.")

if __name__ == "__main__":
    main()

Common Errors & Debugging

Error: 403 Forbidden

  • Cause: The OAuth token does not have the dataactions:write scope.
  • Fix: Go to the Genesys Cloud Admin Console > Users > Applications > Edit your Client Application. Add dataactions:write to the OAuth Scopes. Re-generate the access token.

Error: 422 Unprocessable Entity

  • Cause: The request body contains invalid data. For Data Actions, this often happens if you modify the type (e.g., trying to change a http action to script) or if the timeout value is outside the valid range (1-10000 ms).
  • Fix: Ensure timeout is an integer between 1 and 10000. Do not modify the type field during an update.

Error: 409 Conflict

  • Cause: Another process modified the Data Action between your GET and PUT calls. Genesys Cloud uses optimistic locking.
  • Fix: Implement a retry loop with exponential backoff. Re-fetch the entity before retrying the PUT.

Error: Timeout Still Failing After Update

  • Cause: The external API you are calling is taking longer than the new timeout (e.g., you set it to 5s but the API takes 6s).
  • Fix: Increase the timeout to the maximum 10000ms. If it still fails, your architecture requires asynchronous processing. Use Genesys Cloud Task Router to offload the work or use Webhooks to notify your system asynchronously rather than waiting for a synchronous HTTP response.

Official References