Export All Architect Flows as JSON Using CX as Code CLI

Export All Architect Flows as JSON Using CX as Code CLI

What You Will Build

  • You will create a script that authenticates to Genesys Cloud CX and exports every Architect flow in your organization as a standalone JSON file.
  • This tutorial uses the Genesys Cloud CX REST API via the Python SDK genesyscloud to handle the heavy lifting of authentication and pagination.
  • The code is written in Python 3.8+ and requires the official Genesys Cloud SDK.

Prerequisites

  • OAuth Client Type: Application (Client Credentials) or User OAuth. For bulk exports, an Application client with the architect:flow:read scope is recommended to avoid user session timeouts.
  • Required Scopes: architect:flow:read is mandatory. organization:read is useful for identifying the organization ID if you are operating in a multi-tenant environment, though the default org is usually sufficient.
  • SDK Version: genesyscloud Python SDK version 2.20.0 or higher.
  • Language/Runtime: Python 3.8 or higher.
  • External Dependencies:
    • genesyscloud: The official Genesys Cloud Python SDK.
    • os: Standard library for file system operations.
    • datetime: Standard library for timestamp formatting.

Authentication Setup

The Genesys Cloud CX APIs require OAuth 2.0 bearer tokens. The genesyscloud Python SDK provides a AuthApi client that handles the token exchange, caching, and refresh logic automatically. You must configure the SDK with your client ID, client secret, and environment (e.g., mypurecloud.com, us-east-1.mypurecloud.com).

Initialize the authentication client in your script. This object will be reused for all subsequent API calls.

from genesyscloud import AuthApi, PlatformClientConfiguration

def get_auth_api(client_id: str, client_secret: str, environment: str) -> AuthApi:
    """
    Initialize the AuthApi client for Genesys Cloud.
    
    Args:
        client_id: Your OAuth client ID.
        client_secret: Your OAuth client secret.
        environment: Your Genesys Cloud environment (e.g., mypurecloud.com).
        
    Returns:
        An initialized AuthApi instance.
    """
    # Configure the platform client
    config = PlatformClientConfiguration(
        client_id=client_id,
        client_secret=client_secret,
        environment=environment
    )
    
    # Initialize AuthApi
    auth_api = AuthApi(configuration=config)
    
    return auth_api

Implementation

Step 1: Initialize the Architect API Client

To interact with Architect flows, you need the ArchitectApi client. This client shares the same configuration as the AuthApi. It provides methods for listing, retrieving, and managing flows.

from genesyscloud import ArchitectApi

def get_architect_api(auth_api: AuthApi) -> ArchitectApi:
    """
    Initialize the ArchitectApi client using the existing AuthApi configuration.
    
    Args:
        auth_api: The initialized AuthApi instance.
        
    Returns:
        An initialized ArchitectApi instance.
    """
    # Reuse the configuration from AuthApi
    config = auth_api.configuration
    
    # Initialize ArchitectApi
    architect_api = ArchitectApi(configuration=config)
    
    return architect_api

Step 2: List All Flows with Pagination

The endpoint GET /api/v2/architect/flows returns a paginated list of flows. You must handle pagination to ensure you retrieve every flow in your organization. The SDK simplifies this by providing a list_architect_flows method that accepts page_size and page_token parameters.

The response object contains a entities array of flow summaries and a next_page token. If next_page is None, you have reached the end of the list.

from genesyscloud.rest import ApiException

def list_all_flows(architect_api: ArchitectApi, page_size: int = 100) -> list:
    """
    Retrieve all Architect flows by handling pagination.
    
    Args:
        architect_api: The initialized ArchitectApi instance.
        page_size: Number of flows to retrieve per page (max 100).
        
    Returns:
        A list of flow summary objects.
        
    Raises:
        ApiException: If the API call fails (e.g., 401, 403, 429).
    """
    all_flows = []
    next_page = None
    
    while True:
        try:
            # Call the API to get flows
            response = architect_api.list_architect_flows(
                page_size=page_size,
                page_token=next_page
            )
            
            # Append the current page's entities to the total list
            if response.entities:
                all_flows.extend(response.entities)
            
            # Check if there is a next page
            if response.next_page:
                next_page = response.next_page
            else:
                break
                
        except ApiException as e:
            print(f"Error listing flows: {e.status} - {e.reason}")
            if e.status == 429:
                print("Rate limited. Retrying after delay...")
                import time
                time.sleep(10)
                continue
            else:
                raise

    return all_flows

Step 3: Retrieve Full Flow Definition and Export to JSON

The list endpoint only returns summary information (ID, name, description, version). To export the actual flow logic, you must call GET /api/v2/architect/flows/{flowId} for each flow. This returns the complete JSON definition of the flow, including all nodes, edges, and configuration settings.

You will save each flow as a JSON file named {flowId}.json in a designated directory.

import json
import os
from datetime import datetime

def export_flows(architect_api: ArchitectApi, flows: list, output_dir: str) -> None:
    """
    Retrieve the full definition of each flow and save it as a JSON file.
    
    Args:
        architect_api: The initialized ArchitectApi instance.
        flows: A list of flow summary objects from list_all_flows.
        output_dir: The directory where JSON files will be saved.
    """
    # Create the output directory if it does not exist
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    for flow_summary in flows:
        flow_id = flow_summary.id
        flow_name = flow_summary.name
        
        try:
            # Retrieve the full flow definition
            flow_definition = architect_api.get_architect_flow(flow_id=flow_id)
            
            # Prepare the file name. Sanitize the name to avoid invalid characters.
            safe_name = "".join(c for c in flow_name if c.isalnum() or c in (' ', '_', '-')).rstrip()
            file_name = f"{flow_id}_{safe_name}.json"
            file_path = os.path.join(output_dir, file_name)
            
            # Write the flow definition to a JSON file
            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(flow_definition.to_dict(), f, indent=2)
            
            print(f"Exported flow: {flow_name} (ID: {flow_id})")
            
        except ApiException as e:
            print(f"Error exporting flow {flow_id}: {e.status} - {e.reason}")
            if e.status == 404:
                print(f"Flow {flow_id} not found. It may have been deleted.")
            elif e.status == 429:
                print("Rate limited. Retrying after delay...")
                import time
                time.sleep(10)
                # Retry the current flow
                export_flows(architect_api, [flow_summary], output_dir)
            else:
                continue

Step 4: Handle Versioning and Draft States

Architect flows can have multiple versions. The get_architect_flow endpoint retrieves the latest published version by default. If you need to export draft versions or specific historical versions, you must use the version parameter in the get_architect_flow call.

For a comprehensive export, you might want to export all versions. However, for most “CX as Code” workflows, exporting the current active version is sufficient. The following code snippet demonstrates how to modify the export logic to include a specific version if needed.

def export_flow_with_version(architect_api: ArchitectApi, flow_id: str, version: int, output_dir: str) -> None:
    """
    Export a specific version of a flow.
    
    Args:
        architect_api: The initialized ArchitectApi instance.
        flow_id: The ID of the flow.
        version: The version number to export.
        output_dir: The directory where JSON files will be saved.
    """
    try:
        # Retrieve the specific version of the flow
        flow_definition = architect_api.get_architect_flow(
            flow_id=flow_id,
            version=version
        )
        
        # Prepare the file name
        file_name = f"{flow_id}_v{version}.json"
        file_path = os.path.join(output_dir, file_name)
        
        # Write the flow definition to a JSON file
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(flow_definition.to_dict(), f, indent=2)
            
        print(f"Exported flow version: {flow_id} v{version}")
        
    except ApiException as e:
        print(f"Error exporting flow version {flow_id} v{version}: {e.status} - {e.reason}")

Complete Working Example

Below is the complete, runnable Python script. You must replace YOUR_CLIENT_ID, YOUR_CLIENT_SECRET, and YOUR_ENVIRONMENT with your actual credentials.

import os
import json
import time
from genesyscloud import AuthApi, ArchitectApi, PlatformClientConfiguration
from genesyscloud.rest import ApiException

def get_auth_api(client_id: str, client_secret: str, environment: str) -> AuthApi:
    config = PlatformClientConfiguration(
        client_id=client_id,
        client_secret=client_secret,
        environment=environment
    )
    return AuthApi(configuration=config)

def get_architect_api(auth_api: AuthApi) -> ArchitectApi:
    config = auth_api.configuration
    return ArchitectApi(configuration=config)

def list_all_flows(architect_api: ArchitectApi, page_size: int = 100) -> list:
    all_flows = []
    next_page = None
    
    while True:
        try:
            response = architect_api.list_architect_flows(
                page_size=page_size,
                page_token=next_page
            )
            
            if response.entities:
                all_flows.extend(response.entities)
            
            if response.next_page:
                next_page = response.next_page
            else:
                break
                
        except ApiException as e:
            print(f"Error listing flows: {e.status} - {e.reason}")
            if e.status == 429:
                print("Rate limited. Retrying after 10 seconds...")
                time.sleep(10)
                continue
            else:
                raise

    return all_flows

def export_flows(architect_api: ArchitectApi, flows: list, output_dir: str) -> None:
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    for flow_summary in flows:
        flow_id = flow_summary.id
        flow_name = flow_summary.name
        
        try:
            flow_definition = architect_api.get_architect_flow(flow_id=flow_id)
            
            safe_name = "".join(c for c in flow_name if c.isalnum() or c in (' ', '_', '-')).rstrip()
            file_name = f"{flow_id}_{safe_name}.json"
            file_path = os.path.join(output_dir, file_name)
            
            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(flow_definition.to_dict(), f, indent=2)
            
            print(f"Exported: {file_name}")
            
        except ApiException as e:
            print(f"Error exporting flow {flow_id}: {e.status} - {e.reason}")
            if e.status == 429:
                print("Rate limited. Retrying after 10 seconds...")
                time.sleep(10)
                export_flows(architect_api, [flow_summary], output_dir)
            else:
                continue

def main():
    # Configuration
    CLIENT_ID = "YOUR_CLIENT_ID"
    CLIENT_SECRET = "YOUR_CLIENT_SECRET"
    ENVIRONMENT = "mypurecloud.com"  # Change to your environment
    OUTPUT_DIR = "./exported_flows"
    
    # Initialize APIs
    print("Initializing authentication...")
    auth_api = get_auth_api(CLIENT_ID, CLIENT_SECRET, ENVIRONMENT)
    
    print("Initializing Architect API...")
    architect_api = get_architect_api(auth_api)
    
    # List all flows
    print("Listing all flows...")
    flows = list_all_flows(architect_api)
    print(f"Found {len(flows)} flows.")
    
    # Export flows
    print("Exporting flows to JSON...")
    export_flows(architect_api, flows, OUTPUT_DIR)
    
    print("Export complete.")

if __name__ == "__main__":
    main()

Common Errors & Debugging

Error: 401 Unauthorized

  • Cause: The OAuth token is invalid, expired, or the client credentials are incorrect.
  • Fix: Verify your CLIENT_ID and CLIENT_SECRET. Ensure the client has not been revoked in the Admin Console. The AuthApi client handles token refresh automatically, but if the initial token exchange fails, check your credentials.

Error: 403 Forbidden

  • Cause: The OAuth client does not have the required architect:flow:read scope.
  • Fix: Go to the Admin Console, navigate to Organization > OAuth Clients, edit your client, and ensure the architect:flow:read scope is checked. Save the changes and regenerate your client secret if necessary.

Error: 429 Too Many Requests

  • Cause: You have exceeded the API rate limit. The Genesys Cloud API limits requests to a certain number per minute.
  • Fix: The provided code includes basic retry logic with a 10-second delay. For large organizations with thousands of flows, you may need to increase the delay or implement exponential backoff. Monitor the Retry-After header in the response for precise wait times.

Error: 404 Not Found

  • Cause: The flow ID does not exist or has been deleted.
  • Fix: This can happen if flows are deleted while the export script is running. The code handles this by printing an error and continuing to the next flow. You can choose to ignore these errors or log them for review.

Official References