Launching Architect Flows Programmatically via the Genesys Cloud API
What You Will Build
This tutorial demonstrates how to programmatically trigger a Genesys Cloud Architect flow from an external application using the POST /api/v2/flows/executions endpoint. You will build a Python script that authenticates via OAuth 2.0, constructs a valid execution request with custom input parameters, and handles the asynchronous nature of flow execution to retrieve the final result. This uses the Genesys Cloud REST API v2 and the genesyscloud-python SDK. The primary programming language covered is Python, with JSON payloads for API requests.
Prerequisites
- OAuth Client: A Confidential Client (Client Credentials Grant) or Public Client (Authorization Code Grant) registered in Genesys Cloud. For this server-to-server example, we use Client Credentials.
- Required Scopes:
flow:execution:writeis mandatory to start the flow. If your flow accesses user data or modifies records, you may need additional scopes such asuser:readorfederation:read, depending on the flow’s internal actions. - SDK Version:
genesyscloudPython SDK v8.0.0 or higher. - Runtime: Python 3.8+
- Dependencies:
genesyscloudpydantic(for type validation in examples)
Install the SDK:
pip install genesyscloud
Authentication Setup
Genesys Cloud APIs use OAuth 2.0. For backend services, the Client Credentials flow is the standard pattern. You must obtain an access token before making any API calls. The token expires after 15 minutes, so production code should implement token caching and refresh logic.
The following code initializes the PlatformClient and authenticates using environment variables for security.
import os
from purecloudplatformclientv2 import PlatformClient, Configuration
from purecloudplatformclientv2.rest import ApiException
def get_platform_client() -> PlatformClient:
"""
Initializes and authenticates the Genesys Cloud PlatformClient.
Raises an exception if authentication fails.
"""
# Load credentials from environment variables
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
environment = os.getenv("GENESYS_ENVIRONMENT", "mypurecloud.com")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET must be set.")
# Configure the client
config = Configuration(
host=f"https://{environment}",
access_token=None, # Will be set by the login method
client_id=client_id,
client_secret=client_secret
)
platform_client = PlatformClient(config)
try:
# Authenticate using Client Credentials Grant
platform_client.login()
return platform_client
except ApiException as e:
print(f"Authentication failed: {e.body}")
raise
except Exception as e:
print(f"Unexpected error during authentication: {str(e)}")
raise
Implementation
Step 1: Identify the Flow and Prepare Input Data
Before launching a flow, you must know its unique identifier (flowId). You can find this in the Architect UI by viewing the flow’s settings or by querying the /api/v2/flows endpoint.
Crucially, the POST /api/v2/flows/executions endpoint accepts an optional data payload. This payload is passed into the flow’s execution context. If your flow expects specific inputs (e.g., a customer ID, a transaction amount, or a webhook URL), you must map these to the data object.
Important: The keys in your JSON payload must match the variable names defined in the flow’s “Start” node or any variables exposed to the execution context. If a variable is not defined in the flow, Genesys Cloud will ignore it or throw a validation error if strict mode is enabled.
# Example input data structure
# This JSON will be injected into the flow's execution context.
# Ensure 'customerId' and 'orderId' exist as variables in your Architect flow.
flow_input_data = {
"customerId": "CUST-12345",
"orderId": "ORD-98765",
"source": "external-api-launcher"
}
Step 2: Construct the Execution Request
The POST /api/v2/flows/executions endpoint requires a FlowExecutionRequest object. The most critical field is flowId.
In the Python SDK, this maps to the PostFlowExecutionsRequest class. You must provide the flowId and the optional data dictionary.
from purecloudplatformclientv2.models import PostFlowExecutionsRequest
def build_execution_request(flow_id: str, input_data: dict) -> PostFlowExecutionsRequest:
"""
Constructs the request object for launching a flow.
Args:
flow_id: The UUID of the Architect flow to execute.
input_data: Dictionary of key-value pairs to inject into the flow.
Returns:
PostFlowExecutionsRequest instance.
"""
# Initialize the request object
request = PostFlowExecutionsRequest()
# Set the mandatory flow ID
request.flow_id = flow_id
# Inject custom data if provided
if input_data:
request.data = input_data
return request
Step 3: Launch the Flow and Handle the Response
When you POST to /api/v2/flows/executions, the API returns a 200 OK with a FlowExecution object. This object contains an executionId.
Critical Concept: Genesys Cloud flows are asynchronous. The API call returns immediately after the flow starts, not when it finishes. The status field in the response will typically be running or queued. To know the outcome, you must poll the /api/v2/flows/executions/{executionId} endpoint or use a webhook configured in the flow to notify your system.
For this tutorial, we will demonstrate the launch and immediate polling pattern.
from purecloudplatformclientv2.flow_api import FlowApi
from purecloudplatformclientv2.rest import ApiException
import time
def launch_flow(platform_client: PlatformClient, flow_id: str, input_data: dict) -> str:
"""
Launches the specified flow and returns the execution ID.
Args:
platform_client: Authenticated PlatformClient instance.
flow_id: UUID of the flow.
input_data: Dictionary of inputs.
Returns:
execution_id: The UUID of the newly created execution.
"""
flow_api = FlowApi(platform_client)
# Build the request
req_body = build_execution_request(flow_id, input_data)
try:
# Execute the flow
# api_response contains headers, status, and the body
api_response = flow_api.post_flow_executions(body=req_body)
execution = api_response.body
print(f"Flow launched successfully. Execution ID: {execution.id}")
print(f"Initial Status: {execution.status}")
return execution.id
except ApiException as e:
# Handle specific Genesys Cloud errors
if e.status == 401:
raise RuntimeError("Unauthorized. Token may be expired.")
elif e.status == 403:
raise RuntimeError("Forbidden. Check OAuth scopes (need flow:execution:write).")
elif e.status == 404:
raise RuntimeError(f"Flow not found. Check flow ID: {flow_id}")
elif e.status == 400:
raise RuntimeError(f"Bad Request. Invalid input data or flow configuration: {e.body}")
else:
raise RuntimeError(f"API Error {e.status}: {e.body}")
Step 4: Poll for Completion (Optional but Recommended)
Since the flow runs asynchronously, your external app likely needs to know the result. You can poll the execution status.
Best Practice: Do not poll too frequently. Genesys Cloud enforces rate limits. A 2-5 second interval is reasonable for short flows. For long-running flows, use Webhooks.
def get_execution_status(platform_client: PlatformClient, execution_id: str) -> dict:
"""
Retrieves the current status and result of a flow execution.
"""
flow_api = FlowApi(platform_client)
try:
api_response = flow_api.get_flow_execution(execution_id=execution_id)
execution = api_response.body
return {
"status": execution.status,
"id": execution.id,
"result": execution.result, # Contains the output variables if defined in the flow
"error": execution.error # Contains error details if the flow failed
}
except ApiException as e:
print(f"Error retrieving execution status: {e.body}")
return {"status": "error", "message": str(e.body)}
def wait_for_flow_completion(platform_client: PlatformClient, execution_id: str, max_wait_seconds: int = 60, poll_interval: int = 5) -> dict:
"""
Polls the execution status until completion or timeout.
"""
start_time = time.time()
while time.time() - start_time < max_wait_seconds:
status_info = get_execution_status(platform_client, execution_id)
if status_info["status"] in ["completed", "failed", "abandoned"]:
return status_info
print(f"Polling... Status: {status_info['status']}")
time.sleep(poll_interval)
return {"status": "timeout", "message": "Flow did not complete within the timeout period."}
Complete Working Example
This script combines authentication, flow launching, and result polling into a single runnable module.
import os
import time
from purecloudplatformclientv2 import PlatformClient, Configuration
from purecloudplatformclientv2.flow_api import FlowApi
from purecloudplatformclientv2.models import PostFlowExecutionsRequest
from purecloudplatformclientv2.rest import ApiException
def main():
# 1. Configuration
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
environment = os.getenv("GENESYS_ENVIRONMENT", "mypurecloud.com")
flow_id = os.getenv("GENESYS_FLOW_ID") # e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
if not all([client_id, client_secret, flow_id]):
print("Error: Missing environment variables. Set GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET, and GENESYS_FLOW_ID.")
return
# 2. Authentication
config = Configuration(
host=f"https://{environment}",
client_id=client_id,
client_secret=client_secret
)
platform_client = PlatformClient(config)
try:
platform_client.login()
print("Authenticated successfully.")
except Exception as e:
print(f"Authentication failed: {e}")
return
# 3. Prepare Input Data
# These keys must match variables in your Architect Flow
input_data = {
"customerId": "CUST-12345",
"orderId": "ORD-98765",
"source": "python-sdk-demo"
}
# 4. Launch Flow
flow_api = FlowApi(platform_client)
req_body = PostFlowExecutionsRequest(flow_id=flow_id, data=input_data)
try:
print(f"Launching Flow ID: {flow_id}")
api_response = flow_api.post_flow_executions(body=req_body)
execution = api_response.body
execution_id = execution.id
print(f"Flow launched. Execution ID: {execution_id}")
print(f"Initial Status: {execution.status}")
except ApiException as e:
print(f"Failed to launch flow: {e.status} - {e.body}")
return
# 5. Poll for Result (Optional)
# In production, consider using Webhooks instead of polling
max_wait = 30
poll_interval = 2
start_time = time.time()
while time.time() - start_time < max_wait:
try:
status_resp = flow_api.get_flow_execution(execution_id=execution_id)
current_status = status_resp.body.status
if current_status in ["completed", "failed", "abandoned"]:
break
print(f"Waiting for flow to complete... (Status: {current_status})")
time.sleep(poll_interval)
except ApiException as e:
print(f"Error polling status: {e.body}")
break
# 6. Retrieve Final Result
try:
final_resp = flow_api.get_flow_execution(execution_id=execution_id)
final_execution = final_resp.body
print("\n--- Final Execution Result ---")
print(f"Status: {final_execution.status}")
if final_execution.status == "completed" and final_execution.result:
print("Flow Output Variables:")
for key, value in final_execution.result.items():
print(f" {key}: {value}")
elif final_execution.status == "failed":
print(f"Error: {final_execution.error}")
except ApiException as e:
print(f"Failed to retrieve final result: {e.body}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 400 Bad Request - “Invalid input data”
Cause: The JSON payload provided in the data field contains keys that do not exist as variables in the Architect flow, or the data types mismatch (e.g., sending a string where an integer is expected).
Fix:
- Open the Architect flow in the Genesys Cloud Admin UI.
- Check the “Start” node or any “Set Variable” nodes.
- Ensure the keys in your Python
input_datadictionary exactly match the variable names in the flow. - Verify data types. Genesys Cloud is strict about types. If the flow expects an integer, send an integer, not a string representation.
Error: 403 Forbidden - “Insufficient permissions”
Cause: The OAuth token does not have the flow:execution:write scope.
Fix:
- Go to Genesys Cloud Admin > Security > OAuth 2.0 Clients.
- Edit your client.
- Ensure
flow:execution:writeis added to the “Scopes” list. - Re-generate the token. Note: Existing tokens do not inherit new scopes; you must refresh the token.
Error: 429 Too Many Requests
Cause: You are launching flows or polling status too frequently.
Fix:
- Implement exponential backoff in your polling loop.
- Add a delay between consecutive
POST /api/v2/flows/executionscalls if processing in bulk. - Check the
Retry-Afterheader in the response for the suggested wait time.
Error: Flow Times Out Without Result
Cause: The flow is still running, or it is stuck in a wait state (e.g., waiting for a webhook callback that never arrived).
Fix:
- Check the flow’s execution log in the Admin UI (Analytics > Conversations > Flows).
- Ensure that if the flow uses “Wait for Webhook” or “Wait for Task”, the external system actually sends the completion signal.
- Increase the
max_wait_secondsin your polling logic if the flow is legitimately long-running.