Choosing Between Real-Time Conversations and Historical Analytics in Genesys Cloud
What You Will Build
- This tutorial demonstrates how to retrieve active conversation details using the Real-Time API versus historical conversation metrics using the Analytics API.
- It utilizes the Genesys Cloud Platform REST API v2 endpoints
/api/v2/conversationsand/api/v2/analytics/conversations/details/query. - The code examples are provided in Python using the
genesys-cloud-purecloud-platform-clientSDK.
Prerequisites
- OAuth Client Type: Confidential Client (Client Credentials Flow).
- Required Scopes:
- For Real-Time:
conversation:read,interaction:read. - For Analytics:
analytics:conversations:read,analytics:report:read.
- For Real-Time:
- SDK Version:
genesys-cloud-purecloud-platform-client>= 170.0.0. - Runtime: Python 3.9+.
- Dependencies:
pip install genesys-cloud-purecloud-platform-client httpx.
Authentication Setup
Genesys Cloud requires OAuth 2.0 authentication for all API calls. The Real-Time API and Analytics API share the same authentication mechanism but require different scopes. Using the wrong scope results in a 403 Forbidden error.
The following code initializes the PureCloud client using the standard PureCloudPlatformClientV2 initialization pattern. This pattern handles token acquisition and refresh automatically.
import os
from purecloud_platform_client import PureCloudPlatformClientV2
from purecloud_platform_client.rest import ApiException
def get_purecloud_client():
"""
Initializes and returns an authenticated PureCloud Platform Client.
"""
# 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.")
# Initialize the client
# The SDK handles the OAuth token exchange internally
client = PureCloudPlatformClientV2(
client_id=client_id,
client_secret=client_secret,
environment=environment
)
return client
Implementation
Step 1: Retrieving Real-Time Conversations (/api/v2/conversations)
Use /api/v2/conversations when you need to know what is happening right now. This endpoint returns active interactions. It is stateful and volatile; data returned here disappears once the conversation ends.
Use Cases:
- Displaying a real-time dashboard of active agents.
- Triggering an immediate webhook when a conversation enters a specific state (e.g., “queued”).
- Fetching the current transcript of an ongoing call.
Required Scope: conversation:read
The following code fetches all currently active conversations. Note that this endpoint does not support pagination in the traditional sense because the dataset changes every millisecond. It returns a snapshot of the current state.
from purecloud_platform_client import ConversationApi, ConversationEventType
from purecloud_platform_client.rest import ApiException
import json
def get_active_conversations(client: PureCloudPlatformClientV2):
"""
Fetches all currently active conversations.
"""
api_instance = ConversationApi(client)
try:
# Parameters:
# expand: Optional. Can include 'transcript', 'participants', 'metrics'.
# Be cautious: 'transcript' can be heavy on bandwidth.
# types: Optional. Filter by conversation type (e.g., 'call', 'webchat').
response = api_instance.get_conversations(
expand="participants,metrics",
types="call,webchat"
)
# The response is a ConversationEntityListing
print(f"Found {response.total} active conversations.")
for conversation in response.entities:
print(f"Conversation ID: {conversation.conversation_id}")
print(f"Type: {conversation.type}")
print(f"State: {conversation.state}")
# Access participants
if conversation.participants:
for participant in conversation.participants:
print(f" Participant: {participant.name} (Role: {participant.role})")
print("-" * 40)
return response.entities
except ApiException as e:
# Handle 401 (Unauthorized) or 403 (Forbidden)
if e.status == 403:
print("Error 403: Check if 'conversation:read' scope is granted.")
elif e.status == 401:
print("Error 401: Authentication failed. Check credentials.")
else:
print(f"API Error: {e.status} - {e.body}")
raise
Step 2: Querying Historical Analytics (/api/v2/analytics/conversations/details/query)
Use /api/v2/analytics/conversations/details/query when you need to analyze past events. This endpoint is read-only and aggregates data over time. It is the source of truth for reporting.
Use Cases:
- Calculating Average Handle Time (AHT) for the last week.
- Generating a list of all calls that exceeded a 5-minute wait time.
- Auditing conversation records for compliance.
Required Scope: analytics:conversations:read
The Analytics API uses a query-based model. You send a JSON body defining the time range, metric filters, and groupings. The response is paginated.
from purecloud_platform_client import AnalyticsApi, ConversationDetailsQuery
from purecloud_platform_client.rest import ApiException
from datetime import datetime, timedelta, timezone
def query_historical_conversations(client: PureCloudPlatformClientV2):
"""
Queries historical conversation details for the last 24 hours.
"""
api_instance = AnalyticsApi(client)
# Define the time range
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(days=1)
# Format for ISO 8601
start_str = start_time.isoformat()
end_str = end_time.isoformat()
# Construct the query body
# This query fetches call details where the duration was greater than 60 seconds
query_body = {
"interval": "PT1H", # Aggregate by hour
"dateFrom": start_str,
"dateTo": end_str,
"types": ["call"],
"metrics": ["conversationDuration", "holdDuration"],
"groupBy": ["direction"], # Group by inbound/outbound
"filters": [
{
"type": "metric",
"metric": "conversationDuration",
"operation": "gt",
"value": 60
}
],
"pageSize": 100,
"pageNumber": 1
}
try:
# The SDK method maps to POST /api/v2/analytics/conversations/details/query
response = api_instance.post_analytics_conversations_details_query(
body=query_body
)
print(f"Total records found: {response.total}")
print(f"Page size: {response.page_size}")
print(f"Page number: {response.page_number}")
if response.entities:
for detail in response.entities:
print(f"Interval: {detail.interval}")
print(f"Direction: {detail.direction}")
print(f"Average Duration: {detail.metrics['conversationDuration']['average']} seconds")
print(f"Count: {detail.metrics['conversationDuration']['count']}")
print("-" * 40)
else:
print("No historical data found matching the criteria.")
return response
except ApiException as e:
if e.status == 403:
print("Error 403: Check if 'analytics:conversations:read' scope is granted.")
elif e.status == 400:
print("Error 400: Invalid query body. Check date formats and metric names.")
else:
print(f"API Error: {e.status} - {e.body}")
raise
Step 3: Handling Pagination in Analytics
The Analytics API enforces pagination to prevent large payloads from timing out. The response object contains next_page, previous_page, and total_pages. You must loop through pages to retrieve all results if total exceeds page_size.
def query_all_historical_conversations(client: PureCloudPlatformClientV2):
"""
Iterates through all pages of historical conversation data.
"""
api_instance = AnalyticsApi(client)
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(days=7) # Last 7 days
query_body = {
"interval": "PT1D",
"dateFrom": start_time.isoformat(),
"dateTo": end_time.isoformat(),
"types": ["call"],
"metrics": ["conversationDuration"],
"pageSize": 200,
"pageNumber": 1
}
all_results = []
try:
while True:
response = api_instance.post_analytics_conversations_details_query(
body=query_body
)
if response.entities:
all_results.extend(response.entities)
# Check if there are more pages
if response.next_page is None:
break
# Update query for next page
# Note: The SDK often provides a helper, but manually updating
# pageNumber is explicit and reliable.
query_body["pageNumber"] += 1
print(f"Successfully retrieved {len(all_results)} historical records.")
return all_results
except ApiException as e:
print(f"Failed to retrieve analytics data: {e.body}")
raise
Complete Working Example
This script combines authentication, real-time retrieval, and historical querying into a single executable module. It demonstrates the distinct patterns for each API type.
import os
import sys
import json
from datetime import datetime, timedelta, timezone
from purecloud_platform_client import PureCloudPlatformClientV2, ConversationApi, AnalyticsApi
from purecloud_platform_client.rest import ApiException
def main():
# 1. Authentication
try:
client = get_purecloud_client()
print("Authenticated successfully.")
except Exception as e:
print(f"Authentication failed: {e}")
sys.exit(1)
# 2. Real-Time Fetch
print("\n--- Fetching Real-Time Conversations ---")
try:
active_convos = get_active_conversations(client)
print(f"Retrieved {len(active_convos) if active_convos else 0} active conversations.")
except ApiException as e:
print(f"Real-Time API Error: {e}")
# 3. Historical Fetch
print("\n--- Fetching Historical Analytics ---")
try:
historical_data = query_all_historical_conversations(client)
print(f"Retrieved {len(historical_data)} historical records.")
# Example: Calculate total duration
total_duration = 0
for record in historical_data:
if 'conversationDuration' in record.metrics:
total_duration += record.metrics['conversationDuration']['sum'] or 0
print(f"Total conversation duration in seconds: {total_duration}")
except ApiException as e:
print(f"Analytics API Error: {e}")
def get_purecloud_client():
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.")
return PureCloudPlatformClientV2(
client_id=client_id,
client_secret=client_secret,
environment=environment
)
def get_active_conversations(client: PureCloudPlatformClientV2):
api_instance = ConversationApi(client)
try:
response = api_instance.get_conversations(expand="participants")
return response.entities
except ApiException as e:
raise
def query_all_historical_conversations(client: PureCloudPlatformClientV2):
api_instance = AnalyticsApi(client)
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(days=1)
query_body = {
"interval": "PT1H",
"dateFrom": start_time.isoformat(),
"dateTo": end_time.isoformat(),
"types": ["call"],
"metrics": ["conversationDuration"],
"pageSize": 100,
"pageNumber": 1
}
all_results = []
try:
while True:
response = api_instance.post_analytics_conversations_details_query(body=query_body)
if response.entities:
all_results.extend(response.entities)
if response.next_page is None:
break
query_body["pageNumber"] += 1
return all_results
except ApiException as e:
raise
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden on /api/v2/analytics/...
- Cause: The OAuth token lacks the
analytics:conversations:readscope. The Real-Time scopeconversation:readdoes not grant access to analytics endpoints. - Fix: Update your OAuth client configuration in the Genesys Cloud Admin UI. Navigate to Admin > Security > OAuth > Select Client > Scopes. Add
analytics:conversations:read. Re-authenticate to get a new token.
Error: 429 Too Many Requests
- Cause: You are exceeding the rate limit. The Real-Time API has lower limits than the Analytics API because it is more resource-intensive to maintain state.
- Fix: Implement exponential backoff. The Genesys Cloud SDK does not handle retries automatically for 429s in all versions. Wrap your calls in a retry loop.
import time
def resilient_api_call(func, *args, max_retries=3):
for attempt in range(max_retries):
try:
return func(*args)
except ApiException as e:
if e.status == 429:
wait_time = 2 ** attempt
print(f"Rate limited. Waiting {wait_time} seconds...")
time.sleep(wait_time)
else:
raise
raise Exception("Max retries exceeded for 429 error.")
Error: Empty Response from Analytics Query
- Cause: The
dateFromanddateTorange is too narrow, or thefiltersare too restrictive. - Fix: Verify the time zone. Genesys Cloud Analytics uses UTC. If you pass local time without converting to UTC, you may query a future or past empty range. Ensure
intervalis valid (e.g.,PT1H,PT1D).
Error: TypeError in SDK Initialization
- Cause: Passing environment as a string without the correct format.
- Fix: Ensure the environment variable ends with
.mypurecloud.comor is set tomypurecloud.com(the SDK handles the prefix). Avoid passing justus-east-1unless using the specific region-aware constructor.