Choosing the Right Endpoint: Real-Time Conversations vs. Analytics Data
What You Will Build
- You will build a Python script that retrieves active, real-time conversation details using the Conversations API and a separate script that queries historical conversation metrics using the Analytics API.
- This tutorial uses the Genesys Cloud CX REST API and the
genesyscloudPython SDK. - The examples cover Python, demonstrating the distinct data models, authentication scopes, and use cases for each endpoint family.
Prerequisites
- OAuth Client Type: A Genesys Cloud OAuth application with the
client_credentialsgrant type. - Required Scopes:
- For Conversations API:
conversation:read,presence:read(if checking agent status). - For Analytics API:
analytics:conversations:read,analytics:reports:read.
- For Conversations API:
- SDK Version:
genesyscloudPython SDK v10.0.0 or later. - Runtime: Python 3.9+.
- Dependencies:
pip install genesyscloud
Authentication Setup
Both endpoints require a valid OAuth 2.0 Bearer token. The genesyscloud SDK handles token caching and refresh automatically when initialized correctly. You must set your environment variables or pass credentials directly.
import os
from purecloudplatformclientv2 import PlatformApiClient, Configuration
# Initialize the Platform API Client for authentication
# This client manages the OAuth token lifecycle
platform_api_client = PlatformApiClient(
environment_url=os.environ.get("GENESYS_CLOUD_ENV", "https://api.mypurecloud.com"),
client_id=os.environ.get("GENESYS_CLOUD_CLIENT_ID"),
client_secret=os.environ.get("GENESYS_CLOUD_CLIENT_SECRET"),
grant_type="client_credentials"
)
# Get the configuration object to pass to API clients
configuration = Configuration(
access_token=platform_api_client.get_access_token(),
host=os.environ.get("GENESYS_CLOUD_ENV", "https://api.mypurecloud.com")
)
The PlatformApiClient is the foundation. It does not make data requests itself. It provides the access_token property that other API clients use. If the token expires, subsequent API calls will trigger an automatic refresh if the SDK is configured with the client credentials.
Implementation
Step 1: Retrieving Real-Time Data with /api/v2/conversations
The Conversations API (/api/v2/conversations) represents the current state of the system. It is event-driven and stateful. When you query this endpoint, you receive data about conversations that are currently active (in progress) or recently completed (within a short retention window, typically 24-48 hours depending on configuration).
Use Case: You need to display a live dashboard of active calls, update a CRM with the current caller ID, or trigger a webhook when an agent answers.
Working Code: List Active Conversations
from purecloudplatformclientv2 import ConversationApi
from purecloudplatformclientv2.rest import ApiException
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def get_active_conversations(configuration):
"""
Retrieves currently active conversations.
Scope Required: conversation:read
"""
api_instance = ConversationApi(api_client=configuration)
# Parameters for get_conversations
# domain_id: Optional. If omitted, returns all domains.
# expand: Optional. Use "routing", "wrapup", "media" to get more details.
try:
# Fetch active conversations
response = api_instance.get_conversations(
domain_id="default",
expand=["routing", "wrapup"],
limit=100,
offset=0
)
if response.entities and len(response.entities) > 0:
for conv in response.entities:
logger.info(f"Active Conversation ID: {conv.id}")
logger.info(f" Type: {conv.type}")
logger.info(f" State: {conv.state}")
logger.info(f" Start Time: {conv.start_time}")
# Access routing details if expanded
if conv.routing:
logger.info(f" Queue ID: {conv.routing.queue_id}")
logger.info(f" Agent ID: {conv.routing.agent_id}")
else:
logger.info("No active conversations found.")
return response.entities
except ApiException as e:
logger.error(f"Status: {e.status}")
logger.error(f"Reason: {e.reason}")
logger.error(f"Response: {e.body}")
raise
# Execute
if __name__ == "__main__":
# Assuming 'configuration' is initialized as shown in Authentication Setup
# active_convs = get_active_conversations(configuration)
pass
Expected Response Structure
The response from /api/v2/conversations is a ConversationEntityListing. Key fields include:
id: The unique conversation identifier.type: e.g., “voice”, “chat”, “email”.state: e.g., “ringing”, “connected”, “wrapup”, “closed”.routing: Containsqueue_id,agent_id, andwrapup_code.participants: A list of objects representing each leg of the conversation (customer, agent, IVR).
Error Handling
- 401 Unauthorized: The token has expired or is invalid. The SDK usually retries automatically if configured with client credentials. If not, you must re-authenticate.
- 403 Forbidden: The OAuth client lacks the
conversation:readscope. - 404 Not Found: The
domain_iddoes not exist.
Step 2: Querying Historical Data with /api/v2/analytics/conversations
The Analytics API (/api/v2/analytics/conversations/details/query) is designed for batch processing and historical reporting. It does not return real-time state. It returns aggregated or detailed records of conversations that have been processed and stored in the analytics database. This data is typically available 24-48 hours after the conversation ends, but can be configured for near-real-time in some enterprise setups.
Use Case: You need to generate a weekly report on average handle time (AHT), calculate first call resolution rates, or export conversation transcripts for compliance auditing.
Working Code: Query Historical Conversation Details
from purecloudplatformclientv2 import AnalyticsApi
from purecloudplatformclientv2.rest import ApiException
from purecloudplatformclientv2.models import ConversationDetailsQuery
import logging
from datetime import datetime, timedelta
def get_historical_conversations(configuration):
"""
Queries historical conversation details.
Scope Required: analytics:conversations:read
"""
api_instance = AnalyticsApi(api_client=configuration)
# Define the time range
# Analytics API requires a start and end time
end_time = datetime.utcnow()
start_time = end_time - timedelta(days=7)
# Construct the query body
# The body must be a ConversationDetailsQuery object
query_body = ConversationDetailsQuery(
date_from=start_time.isoformat(),
date_to=end_time.isoformat(),
# Filter by conversation type
type=["voice"],
# Select the fields you want to retrieve
# This is crucial for performance. Only request what you need.
view="conversation",
group_by=[],
interval=None,
# Pagination
page_size=100,
page_token=None
)
try:
# Execute the query
# Note: The endpoint is post_analytics_conversations_details_query
response = api_instance.post_analytics_conversations_details_query(
body=query_body
)
if response.entities and len(response.entities) > 0:
for entity in response.entities:
# The structure of 'entity' depends on the 'view' and 'group_by'
# For view="conversation", entity is a ConversationDetailsRecord
logger.info(f"Historical Conversation ID: {entity.id}")
logger.info(f" Start Time: {entity.start_time}")
logger.info(f" Duration: {entity.duration_seconds} seconds")
logger.info(f" Queue: {entity.queue_id}")
logger.info(f" Agent: {entity.agent_id}")
# Access specific metrics if available
if entity.total_talk_time_seconds:
logger.info(f" Talk Time: {entity.total_talk_time_seconds}s")
if entity.total_hold_time_seconds:
logger.info(f" Hold Time: {entity.total_hold_time_seconds}s")
else:
logger.info("No historical conversations found for the specified range.")
return response.entities
except ApiException as e:
logger.error(f"Status: {e.status}")
logger.error(f"Reason: {e.reason}")
logger.error(f"Response: {e.body}")
raise
# Execute
if __name__ == "__main__":
# Assuming 'configuration' is initialized as shown in Authentication Setup
# historical_convs = get_historical_conversations(configuration)
pass
Expected Response Structure
The response from /api/v2/analytics/conversations/details/query is a ConversationDetailsRecord. Key fields include:
id: The unique conversation identifier.start_time: ISO 8601 timestamp.duration_seconds: Total duration of the conversation.total_talk_time_seconds: Sum of all talk segments.total_hold_time_seconds: Sum of all hold segments.wrap_up_code: The code selected by the agent.queue_id: The queue the conversation was routed to.agent_id: The ID of the agent who handled the conversation.
Error Handling
- 400 Bad Request: The query body is malformed. Common causes include invalid date formats, missing
date_from/date_to, or requesting too many fields in a grouped query. - 429 Too Many Requests: Analytics queries are heavy on the database. If you exceed the rate limit, the API returns a 429. You must implement exponential backoff.
- 500 Internal Server Error: The analytics database is under heavy load. Retry with a delay.
Step 3: Processing Results and Pagination
Both APIs support pagination, but they handle it differently.
Conversations API Pagination
The Conversations API uses limit and offset parameters.
def get_all_active_conversations_paginated(configuration):
api_instance = ConversationApi(api_client=configuration)
all_conversations = []
limit = 100
offset = 0
while True:
try:
response = api_instance.get_conversations(
domain_id="default",
limit=limit,
offset=offset
)
if not response.entities or len(response.entities) == 0:
break
all_conversations.extend(response.entities)
# Check if there are more pages
if response.next_page is None:
break
# The next_page URL contains the new offset
# You can parse it or simply increment offset
offset += limit
except ApiException as e:
logger.error(f"Pagination error: {e.reason}")
break
return all_conversations
Analytics API Pagination
The Analytics API uses page_token in the request body and returns next_page in the response.
def get_all_historical_conversations_paginated(configuration):
api_instance = AnalyticsApi(api_client=configuration)
all_conversations = []
page_token = None
while True:
query_body = ConversationDetailsQuery(
date_from=(datetime.utcnow() - timedelta(days=7)).isoformat(),
date_to=datetime.utcnow().isoformat(),
type=["voice"],
view="conversation",
page_size=100,
page_token=page_token
)
try:
response = api_instance.post_analytics_conversations_details_query(
body=query_body
)
if not response.entities or len(response.entities) == 0:
break
all_conversations.extend(response.entities)
# Check for next page
if response.next_page is None:
break
# Extract the page token from the next_page URL
# The next_page is a full URL. You must parse the page_token query parameter.
from urllib.parse import urlparse, parse_qs
parsed_url = urlparse(response.next_page)
page_token = parse_qs(parsed_url.query).get("page_token", [None])[0]
if page_token is None:
break
except ApiException as e:
logger.error(f"Pagination error: {e.reason}")
break
return all_conversations
Complete Working Example
Below is a complete, runnable Python script that demonstrates both endpoints. It requires the GENESYS_CLOUD_CLIENT_ID and GENESYS_CLOUD_CLIENT_SECRET environment variables to be set.
import os
import logging
from datetime import datetime, timedelta
from urllib.parse import urlparse, parse_qs
from purecloudplatformclientv2 import PlatformApiClient, Configuration, ConversationApi, AnalyticsApi
from purecloudplatformclientv2.models import ConversationDetailsQuery
from purecloudplatformclientv2.rest import ApiException
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def initialize_api_clients():
"""
Initializes the Platform API client and returns the Configuration object.
"""
env_url = os.environ.get("GENESYS_CLOUD_ENV", "https://api.mypurecloud.com")
client_id = os.environ.get("GENESYS_CLOUD_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLOUD_CLIENT_SECRET")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLOUD_CLIENT_ID and GENESYS_CLOUD_CLIENT_SECRET must be set.")
platform_api_client = PlatformApiClient(
environment_url=env_url,
client_id=client_id,
client_secret=client_secret,
grant_type="client_credentials"
)
return Configuration(
access_token=platform_api_client.get_access_token(),
host=env_url
)
def fetch_active_conversations(configuration):
"""
Fetches the first 10 active conversations.
"""
api_instance = ConversationApi(api_client=configuration)
try:
response = api_instance.get_conversations(
domain_id="default",
limit=10,
offset=0,
expand=["routing"]
)
return response.entities if response.entities else []
except ApiException as e:
logger.error(f"Failed to fetch active conversations: {e.reason}")
return []
def fetch_historical_conversations(configuration):
"""
Fetches the first 10 voice conversations from the last 24 hours.
"""
api_instance = AnalyticsApi(api_client=configuration)
end_time = datetime.utcnow()
start_time = end_time - timedelta(hours=24)
query_body = ConversationDetailsQuery(
date_from=start_time.isoformat(),
date_to=end_time.isoformat(),
type=["voice"],
view="conversation",
page_size=10,
page_token=None
)
try:
response = api_instance.post_analytics_conversations_details_query(
body=query_body
)
return response.entities if response.entities else []
except ApiException as e:
logger.error(f"Failed to fetch historical conversations: {e.reason}")
return []
def main():
try:
configuration = initialize_api_clients()
logger.info("=== Fetching Active Conversations ===")
active_convs = fetch_active_conversations(configuration)
for conv in active_convs:
logger.info(f"ID: {conv.id}, State: {conv.state}, Agent: {conv.routing.agent_id if conv.routing else 'None'}")
logger.info("=== Fetching Historical Conversations (Last 24h) ===")
historical_convs = fetch_historical_conversations(configuration)
for conv in historical_convs:
logger.info(f"ID: {conv.id}, Duration: {conv.duration_seconds}s, Agent: {conv.agent_id}")
except Exception as e:
logger.error(f"An unexpected error occurred: {str(e)}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden on Analytics API
Cause: The OAuth client does not have the analytics:conversations:read scope. The Conversations API and Analytics API require different scopes. A client with conversation:read cannot access analytics data.
Fix:
- Go to the Genesys Cloud Admin Console.
- Navigate to Setup > Applications > OAuth Applications.
- Select your application.
- In the Scopes tab, add
analytics:conversations:read. - Save the changes.
- Re-generate the access token.
Error: 429 Too Many Requests on Analytics API
Cause: You are sending too many requests per second. The Analytics API has stricter rate limits than the Conversations API because it queries large datasets.
Fix: Implement exponential backoff.
import time
def api_call_with_retry(func, *args, max_retries=5, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except ApiException as e:
if e.status == 429:
wait_time = 2 ** attempt # Exponential backoff: 2, 4, 8, 16, 32 seconds
logger.warning(f"Rate limited (429). Waiting {wait_time} seconds...")
time.sleep(wait_time)
else:
raise
raise Exception("Max retries exceeded for 429 error")
# Usage
# response = api_call_with_retry(api_instance.post_analytics_conversations_details_query, body=query_body)
Error: 400 Bad Request on Analytics Query
Cause: The date_from or date_to fields are missing, or the view parameter is invalid for the selected group_by fields.
Fix: Ensure the ConversationDetailsQuery object is fully populated.
date_fromanddate_toare mandatory.- If
group_byis empty,viewmust be “conversation” or “summary”. - If
group_byis set,viewmust match the grouping level (e.g., “agent”, “queue”).