Reuse Shared Flows in Genesys Cloud via API
What You Will Build
- A Python script that programmatically updates an inbound IVR flow to invoke a shared sub-flow using the Genesys Cloud Platform SDK.
- This solution uses the Genesys Cloud
PureCloudPlatformClientV2SDK to manage Flow definitions and Flow references. - The programming language covered is Python 3.9+.
Prerequisites
- OAuth Client: A Genesys Cloud OAuth Client with
client_credentialsgrant type. - Required Scopes:
flow:flow:write(to update flows),flow:flow:read(to read existing flows). - SDK Version:
genesys-cloud-purecloud-platform-clientv138.0.0 or later. - Runtime: Python 3.9 or higher.
- External Dependencies:
genesys-cloud-purecloud-platform-clientpython-dotenv(for secure credential management)
Authentication Setup
Genesys Cloud APIs require OAuth 2.0 Bearer tokens. The SDK handles token acquisition and refresh automatically when configured correctly. You must store your Client ID and Client Secret securely. Never hardcode these values.
Create a .env file in your project root:
GENESYS_CLOUD_REGION=us-east-1 # or us-east-2, eu-west-1, etc.
GENESYS_CLOUD_CLIENT_ID=your_client_id_here
GENESYS_CLOUD_CLIENT_SECRET=your_client_secret_here
Initialize the API client in your Python script:
import os
from dotenv import load_dotenv
from purecloudplatformclientv2 import (
Configuration,
ApiClient,
FlowApi,
PlatformApi
)
def get_flow_api_client():
"""
Initializes and returns a configured FlowApi instance.
"""
load_dotenv()
# Load credentials
client_id = os.getenv("GENESYS_CLOUD_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLOUD_CLIENT_SECRET")
region = os.getenv("GENESYS_CLOUD_REGION", "us-east-1")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLOUD_CLIENT_ID and GENESYS_CLOUD_CLIENT_SECRET must be set in environment.")
# Configure OAuth
configuration = Configuration()
configuration.client_id = client_id
configuration.client_secret = client_secret
# Set region if necessary (SDK defaults to us-east-1 if not specified)
if region != "us-east-1":
configuration.region = region
# Create API client
api_client = ApiClient(configuration)
# Initialize Flow API
flow_api = FlowApi(api_client)
return flow_api
Implementation
Step 1: Retrieve the Target Inbound Flow
To update a flow, you must first retrieve its current definition. Genesys Cloud Flows are versioned. You must read the latest version to ensure you are not overwriting concurrent changes.
The endpoint used is GET /api/v2/flows/{flowId}.
def get_flow_definition(flow_api: FlowApi, flow_id: str) -> dict:
"""
Retrieves the full definition of a flow.
Args:
flow_api: The initialized FlowApi client.
flow_id: The UUID of the flow to retrieve.
Returns:
The flow definition dictionary.
"""
try:
# The SDK method gets_flow_by_id returns a Flow object
# We convert it to a dict for easier manipulation
flow_response = flow_api.get_flow_by_id(flow_id)
# Convert SDK object to dictionary
flow_dict = {
"id": flow_response.id,
"version": flow_response.version,
"name": flow_response.name,
"type": flow_response.type,
"description": flow_response.description,
"outcomes": flow_response.outcomes,
"settings": flow_response.settings,
"actions": flow_response.actions,
"outcomesMap": flow_response.outcomes_map,
"outcomesArray": flow_response.outcomes_array
}
return flow_dict
except Exception as e:
# Handle 404 (Flow not found) or 401/403 (Auth issues)
if hasattr(e, 'status') and e.status == 404:
raise ValueError(f"Flow with ID {flow_id} not found.")
elif hasattr(e, 'status') and e.status in [401, 403]:
raise PermissionError(f"Authentication or Authorization failed: {e.reason}")
else:
raise e
Step 2: Define the Shared Sub-Flow Reference
A shared flow is invoked using an invokeflow action. This action requires the ID of the shared flow and a mapping of inputs/outputs.
Assume you have a shared flow with ID shared-flow-uuid-123. You want to pass the caller’s phone number as an input named callerNumber and receive a result named routingDecision.
The critical structure for the action is:
{
"type": "invokeflow",
"name": "InvokeSharedRouting",
"description": "Calls the shared routing logic",
"inputs": {
"callerNumber": "${caller.phoneNumber}"
},
"outputs": {
"routingDecision": "sharedRoutingResult"
},
"flowId": "shared-flow-uuid-123",
"onSuccess": "nextActionId",
"onError": "errorActionId"
}
In the SDK, you construct this as a dictionary or use the specific model classes. Using dictionaries is often simpler for bulk updates.
Step 3: Insert the Action into the Flow Definition
You must insert the invokeflow action into the actions list of the retrieved flow. Ensure you assign a unique ID to the new action and update the outcomesMap if necessary (though invokeflow typically relies on onSuccess/onError transitions).
def add_invoke_flow_action(flow_dict: dict, shared_flow_id: str, next_action_id: str, error_action_id: str) -> dict:
"""
Adds an invokeflow action to the flow definition.
Args:
flow_dict: The current flow definition dictionary.
shared_flow_id: The ID of the shared flow to invoke.
next_action_id: The ID of the action to run on success.
error_action_id: The ID of the action to run on error.
Returns:
The updated flow definition dictionary.
"""
import uuid
# Generate a unique ID for the new action
new_action_id = str(uuid.uuid4())
# Define the invokeflow action
invoke_action = {
"id": new_action_id,
"type": "invokeflow",
"name": "InvokeSharedLogic",
"description": "Delegates to shared flow for common logic",
"inputs": {
"callerNumber": "${caller.phoneNumber}",
"language": "${caller.language}"
},
"outputs": {
"result": "sharedFlowResult"
},
"flowId": shared_flow_id,
"onSuccess": next_action_id,
"onError": error_action_id
}
# Append the action to the existing actions list
if "actions" not in flow_dict:
flow_dict["actions"] = []
flow_dict["actions"].append(invoke_action)
# Note: You must also ensure that the previous action in the flow
# points to this new action's ID as its 'next' step.
# For this example, we assume the first action is the entry point
# and we are inserting this as the second action.
if len(flow_dict["actions"]) > 1:
# Update the first action to point to our new invoke action
first_action = flow_dict["actions"][0]
if "next" in first_action:
first_action["next"] = new_action_id
else:
first_action["next"] = new_action_id
return flow_dict
Step 4: Update the Flow
To save the changes, you must send a PUT request to /api/v2/flows/{flowId}. The request body must include the version number from the retrieved flow. Genesys Cloud uses optimistic locking; if the version number does not match the current server version, the update will fail with a 409 Conflict.
def update_flow_definition(flow_api: FlowApi, flow_id: str, flow_dict: dict) -> str:
"""
Updates the flow definition in Genesys Cloud.
Args:
flow_api: The initialized FlowApi client.
flow_id: The UUID of the flow to update.
flow_dict: The updated flow definition dictionary.
Returns:
The ID of the updated flow.
"""
# Construct the request body
# The SDK expects a Flow object or a dict that maps to Flow fields
# We must ensure 'version' is included for optimistic locking
try:
# Update the flow
# The SDK method update_flow takes flow_id and the flow object/dict
response = flow_api.update_flow(flow_id=flow_id, body=flow_dict)
return response.id
except Exception as e:
if hasattr(e, 'status'):
if e.status == 409:
raise ConflictError("Flow version conflict. Another user may have modified the flow. Please retry.")
elif e.status == 422:
raise ValueError(f"Unprocessable Entity. Invalid flow definition: {e.reason}")
raise e
Complete Working Example
This script retrieves an inbound flow, inserts an invokeflow action to call a shared flow, and saves the changes.
import os
import sys
import uuid
from dotenv import load_dotenv
from purecloudplatformclientv2 import (
Configuration,
ApiClient,
FlowApi
)
def get_flow_api_client():
load_dotenv()
client_id = os.getenv("GENESYS_CLOUD_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLOUD_CLIENT_SECRET")
region = os.getenv("GENESYS_CLOUD_REGION", "us-east-1")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLOUD_CLIENT_ID and GENESYS_CLOUD_CLIENT_SECRET must be set.")
configuration = Configuration()
configuration.client_id = client_id
configuration.client_secret = client_secret
if region != "us-east-1":
configuration.region = region
api_client = ApiClient(configuration)
return FlowApi(api_client)
def main():
# Configuration
INBOUND_FLOW_ID = os.getenv("INBOUND_FLOW_ID", "your-inbound-flow-uuid")
SHARED_FLOW_ID = os.getenv("SHARED_FLOW_ID", "your-shared-flow-uuid")
NEXT_ACTION_ID = "existing-action-id-after-invoke" # Replace with actual ID from your flow
ERROR_ACTION_ID = "error-handling-action-id" # Replace with actual ID
if INBOUND_FLOW_ID == "your-inbound-flow-uuid" or SHARED_FLOW_ID == "your-shared-flow-uuid":
print("Error: Please set INBOUND_FLOW_ID and SHARED_FLOW_ID in your .env file.")
sys.exit(1)
try:
# 1. Initialize Client
flow_api = get_flow_api_client()
print(f"Authenticating...")
# 2. Get Current Flow
print(f"Retrieving flow {INBOUND_FLOW_ID}...")
flow_response = flow_api.get_flow_by_id(INBOUND_FLOW_ID)
# Convert to dict for manipulation
flow_dict = {
"id": flow_response.id,
"version": flow_response.version,
"name": flow_response.name,
"type": flow_response.type,
"description": flow_response.description,
"outcomes": flow_response.outcomes,
"settings": flow_response.settings,
"actions": flow_response.actions,
"outcomesMap": flow_response.outcomes_map,
"outcomesArray": flow_response.outcomes_array
}
# 3. Prepare InvokeFlow Action
new_action_id = str(uuid.uuid4())
invoke_action = {
"id": new_action_id,
"type": "invokeflow",
"name": "CallSharedFlow",
"description": "Invokes the shared business logic flow",
"inputs": {
"callerNumber": "${caller.phoneNumber}"
},
"outputs": {
"result": "sharedResult"
},
"flowId": SHARED_FLOW_ID,
"onSuccess": NEXT_ACTION_ID,
"onError": ERROR_ACTION_ID
}
# 4. Insert Action into Flow
# For simplicity, we append to the end and assume the previous action
# will be manually updated or this is a new flow.
# In production, you must update the 'next' pointer of the preceding action.
if not flow_dict.get("actions"):
flow_dict["actions"] = []
# Example: Insert at index 1 (after the first action)
if len(flow_dict["actions"]) > 0:
# Update the last action's 'next' to point to our new action
last_action = flow_dict["actions"][-1]
last_action["next"] = new_action_id
flow_dict["actions"].append(invoke_action)
# 5. Save Flow
print(f"Updating flow {INBOUND_FLOW_ID}...")
update_response = flow_api.update_flow(INBOUND_FLOW_ID, body=flow_dict)
print(f"Success! Flow updated. New Version: {update_response.version}")
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 409 Conflict (Version Mismatch)
What causes it: You retrieved the flow, modified it, and attempted to save it, but another user or process updated the flow in between. The version number in your request body does not match the current server version.
How to fix it: Implement a retry mechanism that re-fetches the flow, re-applies your changes, and retries the update.
def update_flow_with_retry(flow_api: FlowApi, flow_id: str, flow_dict: dict, max_retries: int = 3) -> str:
for attempt in range(max_retries):
try:
response = flow_api.update_flow(flow_id=flow_id, body=flow_dict)
return response.id
except Exception as e:
if hasattr(e, 'status') and e.status == 409:
# Re-fetch the flow to get the latest version
fresh_flow = flow_api.get_flow_by_id(flow_id)
# Re-apply your changes to the fresh flow data
flow_dict["version"] = fresh_flow.version
# Re-apply other modifications here
continue
else:
raise e
raise Exception("Max retries exceeded due to version conflicts.")
Error: 422 Unprocessable Entity (Invalid Flow Definition)
What causes it: The flow definition JSON is invalid. Common causes include:
- Missing
nextpointer in an action. - Referencing a non-existent action ID in
onSuccessoronError. - Circular references in the flow logic.
How to fix it: Validate the flow structure before sending. Ensure every action has a valid next, onSuccess, or onError pointer that references an existing action ID in the same flow.
Error: 403 Forbidden (Insufficient Scopes)
What causes it: The OAuth token does not have the flow:flow:write scope.
How to fix it: Regenerate the OAuth token using a client that has the flow:flow:write scope assigned in the Genesys Cloud Admin Console under Users > Applications > OAuth.