Querying Live Conversations vs. Analytics History in Genesys Cloud CX
What You Will Build
- You will build a Python script that retrieves real-time conversation data using the Conversations API and historical analytics data using the Analytics API.
- This tutorial uses the Genesys Cloud CX REST API v2 and the
genesys-cloud-sdk-pythonpackage. - The implementation covers Python 3.9+ with
httpxfor raw HTTP requests to illustrate the underlying mechanics.
Prerequisites
- OAuth Client: A Genesys Cloud CX OAuth Client with
confidentialaccess type. - Required Scopes:
- For Conversations API:
conversation:read,conversation:monitoring:read - For Analytics API:
analytics:conversation:read,analytics:interaction:read
- For Conversations API:
- SDK Version:
genesys-cloud-sdk-python>= 130.0.0 - Language/Runtime: Python 3.9+
- External Dependencies:
httpx(for raw HTTP examples)python-dotenv(for credential management)genesys-cloud-sdk-python
Authentication Setup
Genesys Cloud CX uses OAuth 2.0. The Conversations API and Analytics API share the same authentication mechanism, but they serve fundamentally different purposes. The Conversations API operates on the live event bus, while the Analytics API queries the data warehouse.
You must obtain a bearer token before making any requests.
import httpx
import os
from dotenv import load_dotenv
load_dotenv()
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
ORGANIZATION_ID = os.getenv("GENESYS_ORG_ID")
def get_access_token() -> str:
"""
Obtains an OAuth 2.0 bearer token from Genesys Cloud.
"""
url = f"https://api.mypurecloud.com/oauth/token"
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
}
# Use httpx for synchronous requests in this example
with httpx.Client() as client:
response = client.post(url, headers=headers, data=data)
if response.status_code != 200:
raise Exception(f"Failed to obtain token: {response.text}")
token_data = response.json()
return token_data["access_token"]
ACCESS_TOKEN = get_access_token()
AUTH_HEADERS = {
"Authorization": f"Bearer {ACCESS_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json"
}
Implementation
Step 1: Understanding the Conversations API (Real-Time)
The /api/v2/conversations endpoints are designed for operational use cases. They return data about conversations that are currently active or have recently concluded (typically within the last 24-48 hours, depending on the specific endpoint).
Use this API when you need to:
- Monitor active calls in a dashboard.
- Trigger webhooks based on real-time conversation events.
- Retrieve metadata for a conversation that is happening right now.
Endpoint: GET /api/v2/conversations
This endpoint returns a list of conversations. It does not return detailed metrics (like handle time) unless you drill down into specific conversation types (e.g., /api/v2/conversations/calls/{id}).
def list_active_conversations(token: str) -> dict:
"""
Retrieves a list of active conversations.
Scope required: conversation:read
"""
url = "https://api.mypurecloud.com/api/v2/conversations"
params = {
"pageSize": 100,
"pageNumber": 1
}
with httpx.Client() as client:
response = client.get(url, headers=AUTH_HEADERS, params=params)
if response.status_code == 401:
raise Exception("Unauthorized. Token is invalid or expired.")
if response.status_code == 403:
raise Exception("Forbidden. Check OAuth scopes.")
if response.status_code == 429:
# Retry logic should be implemented in production
raise Exception("Rate limited. Wait before retrying.")
return response.json()
# Execute
active_convs = list_active_conversations(ACCESS_TOKEN)
print(f"Found {len(active_convs.get('entities', []))} active conversations.")
Expected Response Structure:
{
"entities": [
{
"id": "conv-12345-67890",
"type": "call",
"createdTimestamp": "2023-10-27T10:00:00.000Z",
"updatedTimestamp": "2023-10-27T10:05:00.000Z",
"participants": [
{
"id": "part-111",
"routing": {
"queueName": "Support Queue",
"state": "connected"
}
}
]
}
],
"pageSize": 100,
"pageNumber": 1,
"pageCount": 1
}
Step 2: Understanding the Analytics API (Historical)
The /api/v2/analytics/conversations endpoints are designed for reporting use cases. They query the Genesys Cloud data warehouse. Data availability typically has a delay of 1-4 hours (real-time analytics may have lower latency, but detailed analytics are batched).
Use this API when you need to:
- Generate end-of-day reports.
- Calculate metrics like Average Handle Time (AHT), Abandon Rate, or Service Level.
- Retrieve conversation data older than 48 hours.
Endpoint: POST /api/v2/analytics/conversations/details/query
Unlike the Conversations API, the Analytics API uses a query body rather than query parameters. This allows for complex filtering and aggregation.
def query_historical_conversations(token: str) -> dict:
"""
Retrieves detailed historical conversation data.
Scope required: analytics:conversation:read
"""
url = "https://api.mypurecloud.com/api/v2/analytics/conversations/details/query"
# Define the time range (ISO 8601 format)
# Analytics data is not available for the current minute; use a past range.
query_body = {
"interval": "2023-10-26T00:00:00.000Z/2023-10-26T23:59:59.999Z",
"groupBy": ["conversationType"],
"metrics": [
"handled",
"abandoned",
"total",
"serviceLevel"
],
"filter": {
"type": "and",
"clauses": [
{
"type": "eq",
"field": "routing.queue.name",
"value": "Support Queue"
}
]
}
}
with httpx.Client() as client:
response = client.post(url, headers=AUTH_HEADERS, json=query_body)
if response.status_code == 400:
print(f"Bad Request: {response.text}")
raise Exception("Invalid query body. Check interval format or metric names.")
if response.status_code == 401:
raise Exception("Unauthorized.")
return response.json()
# Execute
analytics_data = query_historical_conversations(ACCESS_TOKEN)
print(f"Analytics result keys: {analytics_data.keys()}")
Expected Response Structure:
{
"interval": "2023-10-26T00:00:00.000Z/2023-10-26T23:59:59.999Z",
"groupBy": ["conversationType"],
"metrics": [
"handled",
"abandoned",
"total",
"serviceLevel"
],
"results": [
{
"conversationType": "call",
"metrics": {
"handled": {
"value": 150,
"unit": "count"
},
"abandoned": {
"value": 5,
"unit": "count"
},
"total": {
"value": 155,
"unit": "count"
},
"serviceLevel": {
"value": 0.85,
"unit": "ratio"
}
}
}
]
}
Step 3: Processing Results and Handling Pagination
Both APIs support pagination, but they handle it differently.
Conversations API Pagination:
Uses pageSize and pageNumber or nextPageId. It is cursor-based or page-based depending on the endpoint.
Analytics API Pagination:
Uses nextPageToken in the response. You must include this token in the nextPageToken field of your next request body.
def get_all_analytics_pages(token: str, initial_query: dict) -> list:
"""
Iterates through all pages of analytics results.
"""
all_results = []
current_query = initial_query.copy()
with httpx.Client() as client:
while True:
response = client.post(
"https://api.mypurecloud.com/api/v2/analytics/conversations/details/query",
headers=AUTH_HEADERS,
json=current_query
)
if response.status_code != 200:
raise Exception(f"Analytics query failed: {response.status_code} {response.text}")
data = response.json()
all_results.extend(data.get("results", []))
# Check for next page
next_page_token = data.get("nextPageToken")
if not next_page_token:
break
current_query["nextPageToken"] = next_page_token
return all_results
Complete Working Example
This script demonstrates both APIs side-by-side. It first checks for active conversations, then queries historical data for the previous day.
import httpx
import os
import json
from datetime import datetime, timedelta
from dotenv import load_dotenv
load_dotenv()
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
def get_token() -> str:
url = "https://api.mypurecloud.com/oauth/token"
data = {
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
}
with httpx.Client() as client:
res = client.post(url, data=data, headers={"Content-Type": "application/x-www-form-urlencoded"})
res.raise_for_status()
return res.json()["access_token"]
def fetch_active_conversations(token: str) -> list:
"""Fetches active conversations using Conversations API."""
url = "https://api.mypurecloud.com/api/v2/conversations"
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/json"
}
params = {"pageSize": 20}
with httpx.Client() as client:
res = client.get(url, headers=headers, params=params)
if res.status_code == 401:
raise Exception("Token Invalid")
res.raise_for_status()
return res.json().get("entities", [])
def fetch_historical_metrics(token: str, queue_name: str = "Support Queue") -> dict:
"""Fetches historical metrics using Analytics API."""
url = "https://api.mypurecloud.com/api/v2/analytics/conversations/details/query"
# Define yesterday's date range
yesterday = datetime.utcnow() - timedelta(days=1)
start = yesterday.replace(hour=0, minute=0, second=0, microsecond=0).isoformat() + "Z"
end = yesterday.replace(hour=23, minute=59, second=59, microsecond=999999).isoformat() + "Z"
query = {
"interval": f"{start}/{end}",
"groupBy": ["conversationType"],
"metrics": ["handled", "abandoned"],
"filter": {
"type": "and",
"clauses": [
{
"type": "eq",
"field": "routing.queue.name",
"value": queue_name
}
]
}
}
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
with httpx.Client() as client:
res = client.post(url, headers=headers, json=query)
if res.status_code == 400:
print(f"Query Error: {res.text}")
return {}
res.raise_for_status()
return res.json()
def main():
token = get_token()
# 1. Real-Time Check
print("--- Real-Time Conversations ---")
try:
active = fetch_active_conversations(token)
print(f"Active Conversations: {len(active)}")
if active:
print(f"Sample ID: {active[0]['id']}")
except Exception as e:
print(f"Error fetching active conversations: {e}")
# 2. Historical Check
print("\n--- Historical Analytics (Yesterday) ---")
try:
history = fetch_historical_metrics(token)
if history.get("results"):
for r in history["results"]:
print(f"Type: {r['conversationType']}")
print(f" Handled: {r['metrics']['handled']['value']}")
print(f" Abandoned: {r['metrics']['abandoned']['value']}")
else:
print("No historical data found for the specified filter.")
except Exception as e:
print(f"Error fetching analytics: {e}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 400 Bad Request on Analytics API
Cause: The interval format is incorrect, or the requested metrics are not valid for the selected groupBy fields.
Fix: Ensure the interval is in ISO 8601 format with a / separator. Verify that the metrics exist in the Genesys Cloud Analytics dictionary. Use the Analytics API explorer in the developer portal to validate metric names.
Error: 401 Unauthorized on Conversations API
Cause: The OAuth token is missing the conversation:read scope.
Fix: Regenerate the token with the correct scopes. Check the OAuth client configuration in Genesys Cloud Admin > Security > OAuth Clients.
Error: 429 Too Many Requests
Cause: You have exceeded the rate limit for the API endpoint. Conversations API has a lower rate limit than Analytics API.
Fix: Implement exponential backoff. Do not poll the Conversations API more than once every 5-10 seconds per tenant. For Analytics, use caching for repeated queries.
Error: Empty Results in Analytics
Cause: The time interval is too recent (data delay) or the filter is too restrictive.
Fix: Query data from at least 24 hours ago. Broaden the filter to check if any data exists at all.