Constructing an Analytics API Aggregation Query for Queue and Media Type
What You Will Build
- This tutorial demonstrates how to query the Genesys Cloud Analytics API to retrieve aggregated conversation metrics grouped by queue and media type.
- The code uses the Genesys Cloud REST API via the Python SDK
genesyscloudto construct complex aggregation filters. - The implementation is written in Python 3.8+ using the official PureCloud Platform Client V2 SDK.
Prerequisites
- OAuth Client Type: A Service Account with the
analytics:analytics:viewscope. - SDK Version:
genesyscloud>= 1.0.0 (Python). - Runtime: Python 3.8 or higher.
- Dependencies: Install the SDK via pip:
pip install genesyscloud.
Authentication Setup
Genesys Cloud uses OAuth 2.0 for authentication. For server-to-server integrations, a Service Account is the standard approach. The Python SDK handles token caching and refreshing automatically when configured with a service account.
You must create a service account in the Genesys Cloud Admin Console with the analytics:analytics:view scope. Download the client ID and client secret.
import os
from genesyscloud import Configuration, ApiClient, PureCloudAnalyticsApi
def get_analytics_api_client() -> PureCloudAnalyticsApi:
"""
Initializes the Genesys Cloud Analytics API client using environment variables.
"""
# Load credentials from environment variables
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
base_url = os.getenv("GENESYS_BASE_URL", "https://api.mypurecloud.com")
if not client_id or not client_secret:
raise EnvironmentError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET must be set.")
# Configure the SDK
config = Configuration()
config.host = base_url
config.oauth_client_id = client_id
config.oauth_client_secret = client_secret
# Create the API client
api_client = ApiClient(configuration=config)
# Initialize the Analytics API instance
analytics_api = PureCloudAnalyticsApi(api_client)
return analytics_api
Implementation
Step 1: Define the Query Parameters
The core of the Analytics API is the AnalyticsQuery object. To group data by queue and media type, you must define specific filterGroups and groupBy clauses.
The endpoint /api/v2/analytics/conversations/details/query returns raw conversation details. However, for aggregation without fetching every single conversation record, we often use /api/v2/analytics/conversations/summary/query or the details endpoint with aggregation filters. In this tutorial, we will use the Summary Query endpoint because it is designed for high-level aggregation metrics (like handle time, wait time, total conversations) grouped by dimensions.
The required fields for our query are:
- Time Frame: A
startTimeandendTimein ISO 8601 format. - Filter Groups: To restrict data to specific queues if necessary (optional, but recommended for performance).
- Group By: The dimensions
queueIdandmediaType. - Metrics: The specific KPIs you want to aggregate (e.g.,
total,avgHandleTime).
from genesyscloud.models import AnalyticsQuery, FilterGroup, Filter, GroupBy, Metric
from datetime import datetime, timedelta
import pytz
def build_aggregation_query() -> AnalyticsQuery:
"""
Constructs an AnalyticsQuery object grouped by queue and media type.
"""
# Define the time range (last 7 days)
end_time = datetime.now(pytz.utc)
start_time = end_time - timedelta(days=7)
# Format times as ISO 8601 strings
start_time_str = start_time.isoformat()
end_time_str = end_time.isoformat()
# Define the filter group (Optional: Filter by specific queues)
# If omitted, it queries all accessible queues.
# filter_group = FilterGroup(
# clauses=[
# Filter(
# type="queueid",
# values=["queue-id-1", "queue-id-2"]
# )
# ]
# )
# Define the Group By dimensions
# We want to group by Queue ID and Media Type
group_by = GroupBy(
by=[
"queueId",
"mediaType"
]
)
# Define the metrics to aggregate
# Common metrics: total, avgHandleTime, avgWaitTime, abandonRate
metrics = [
"total",
"avgHandleTime",
"avgWaitTime",
"abandonRate"
]
# Construct the final query object
query = AnalyticsQuery(
query_type="summary",
start_time=start_time_str,
end_time=end_time_str,
group_by=group_by,
metrics=metrics
# filter_groups=[filter_group] if you defined one above
)
return query
Step 2: Execute the Query and Handle Pagination
The Genesys Cloud Analytics API returns paginated results. You must handle the nextPageUri to retrieve all data if the result set is large. The SDK provides a convenient async_call or synchronous method. We will use the synchronous post_analytics_conversations_summary_query method.
def execute_query(analytics_api: PureCloudAnalyticsApi, query: AnalyticsQuery) -> dict:
"""
Executes the analytics query and handles pagination.
Returns a combined list of all aggregated buckets.
"""
all_buckets = []
try:
# Initial request
response = analytics_api.post_analytics_conversations_summary_query(body=query)
# Process the first page
if response.buckets:
all_buckets.extend(response.buckets)
# Handle pagination
next_page_uri = response.next_page_uri
max_pages = 100 # Safety break to prevent infinite loops in case of API issues
page_count = 0
while next_page_uri and page_count < max_pages:
page_count += 1
# The SDK allows passing the next_page_uri directly to the same method
# Note: In some SDK versions, you may need to use the raw API client for pagination
# Here we assume the SDK handles the URI correctly or we fetch via the URI directly
# Alternative robust pagination using the raw API client if SDK doesn't support next_page_uri directly
# response = analytics_api.api_client.call_api(
# path='/api/v2/analytics/conversations/summary/query',
# method='POST',
# body=query, # Some endpoints require re-sending body with pagination token
# ...
# )
# Standard SDK approach for summary query pagination often involves
# reconstructing the request or using the provided next_page_uri in a GET request
# if the SDK supports it.
# For this tutorial, we will use the simpler approach:
# Many summary queries fit in one page. If not, you must follow the next_page_uri.
# Correct SDK Pagination Pattern for Summary Query:
# The response object contains 'next_page_uri'. You must issue a GET request to that URI.
if next_page_uri:
# Fetch next page using the raw API client for flexibility
next_response = analytics_api.api_client.call_api(
path=next_page_uri,
method='GET',
response_type='AnalyticsSummaryQueryResponse'
)
if next_response.buckets:
all_buckets.extend(next_response.buckets)
next_page_uri = next_response.next_page_uri
else:
break
except Exception as e:
print(f"Error executing query: {e}")
raise
return all_buckets
Note: The pagination logic above uses the raw call_api method because the high-level SDK methods for post_analytics_conversations_summary_query often do not automatically follow next_page_uri in a loop. This is a common pattern in Genesys Cloud SDKs.
Step 3: Map Queue IDs and Media Types to Human-Readable Names
The API returns queueId (UUIDs) and mediaType (strings like “voice”, “chat”, “email”). To make the data useful, you should map these to names. We will fetch the queue definitions to create a lookup map.
from genesyscloud import PureCloudRoutingApi
def get_queue_lookup(routing_api: PureCloudRoutingApi) -> dict:
"""
Fetches all queues and returns a dictionary mapping queueId to name.
"""
queue_map = {}
try:
# Get all queues (may require pagination if you have many queues)
response = routing_api.get_routing_queues(expand="name")
if response.entities:
for queue in response.entities:
queue_map[queue.id] = queue.name
except Exception as e:
print(f"Error fetching queues: {e}")
return queue_map
def format_results(buckets: list, queue_map: dict) -> list:
"""
Formats the raw API buckets into a readable list of dictionaries.
"""
formatted_data = []
for bucket in buckets:
# Extract dimensions
queue_id = bucket.dimensions.get("queueId")
media_type = bucket.dimensions.get("mediaType")
# Map queue ID to name
queue_name = queue_map.get(queue_id, f"Unknown Queue ({queue_id})")
# Extract metrics
metrics = bucket.metrics
formatted_record = {
"queue_name": queue_name,
"queue_id": queue_id,
"media_type": media_type,
"total_conversations": metrics.get("total", 0),
"avg_handle_time_sec": metrics.get("avgHandleTime", 0),
"avg_wait_time_sec": metrics.get("avgWaitTime", 0),
"abandon_rate": metrics.get("abandonRate", 0)
}
formatted_data.append(formatted_record)
return formatted_data
Complete Working Example
This script combines all steps into a single runnable module. It authenticates, builds the query, executes it, resolves queue names, and prints the results.
import os
import json
from datetime import datetime, timedelta
import pytz
from genesyscloud import Configuration, ApiClient, PureCloudAnalyticsApi, PureCloudRoutingApi
from genesyscloud.models import AnalyticsQuery, GroupBy
def main():
# 1. Authentication
try:
api_client = ApiClient()
api_client.configuration.host = os.getenv("GENESYS_BASE_URL", "https://api.mypurecloud.com")
api_client.configuration.oauth_client_id = os.getenv("GENESYS_CLIENT_ID")
api_client.configuration.oauth_client_secret = os.getenv("GENESYS_CLIENT_SECRET")
analytics_api = PureCloudAnalyticsApi(api_client)
routing_api = PureCloudRoutingApi(api_client)
except Exception as e:
print(f"Authentication failed: {e}")
return
# 2. Build Query
end_time = datetime.now(pytz.utc)
start_time = end_time - timedelta(days=7)
query = AnalyticsQuery(
query_type="summary",
start_time=start_time.isoformat(),
end_time=end_time.isoformat(),
group_by=GroupBy(by=["queueId", "mediaType"]),
metrics=["total", "avgHandleTime", "avgWaitTime", "abandonRate"]
)
# 3. Fetch Queue Lookup Map
print("Fetching queue definitions...")
queue_map = {}
try:
queues_response = routing_api.get_routing_queues(expand="name")
if queues_response.entities:
for q in queues_response.entities:
queue_map[q.id] = q.name
except Exception as e:
print(f"Warning: Could not fetch queues: {e}")
# 4. Execute Analytics Query
print("Executing analytics query...")
all_buckets = []
try:
response = analytics_api.post_analytics_conversations_summary_query(body=query)
if response.buckets:
all_buckets.extend(response.buckets)
# Pagination handling
next_page_uri = response.next_page_uri
page = 0
while next_page_uri and page < 50:
page += 1
next_response = analytics_api.api_client.call_api(
path=next_page_uri,
method='GET',
response_type='AnalyticsSummaryQueryResponse'
)
if next_response.buckets:
all_buckets.extend(next_response.buckets)
next_page_uri = next_response.next_page_uri
except Exception as e:
print(f"Query execution failed: {e}")
return
# 5. Format and Display Results
print(f"\nFound {len(all_buckets)} aggregated buckets.")
print("-" * 80)
print(f"{'Queue Name':<30} | {'Media Type':<10} | {'Total Conv':<10} | {'Avg Handle (s)':<15} | {'Abandon Rate':<10}")
print("-" * 80)
for bucket in all_buckets:
q_id = bucket.dimensions.get("queueId")
q_name = queue_map.get(q_id, "Unknown")
media = bucket.dimensions.get("mediaType", "N/A")
metrics = bucket.metrics
total = metrics.get("total", 0)
aht = metrics.get("avgHandleTime", 0)
abandon = metrics.get("abandonRate", 0)
# Format abandon rate as percentage
abandon_pct = f"{abandon:.2%}" if abandon else "0.00%"
print(f"{q_name:<30} | {media:<10} | {total:<10} | {aht:<15.2f} | {abandon_pct:<10}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 401 Unauthorized
- Cause: The OAuth token is invalid, expired, or the client credentials are incorrect.
- Fix: Verify
GENESYS_CLIENT_IDandGENESYS_CLIENT_SECRETin your environment variables. Ensure the service account has not been deactivated in the Admin Console.
Error: 403 Forbidden
- Cause: The service account lacks the required scope.
- Fix: Ensure the service account has the
analytics:analytics:viewscope. If you are filtering by specific queues, ensure the service account has access to those queues.
Error: 400 Bad Request - Invalid Query
- Cause: The
AnalyticsQueryobject is malformed. Common issues include invalid ISO 8601 timestamps or unsupported metrics. - Fix: Check that
start_timeandend_timeare in strict ISO 8601 format (e.g.,2023-10-01T00:00:00Z). Ensure themetricslist contains valid metric names for theconversationsdomain.
Error: 429 Too Many Requests
- Cause: You have exceeded the rate limit for the Analytics API.
- Fix: Implement exponential backoff. The Python SDK does not automatically retry 429s for analytics queries in all versions. Wrap the API call in a retry loop.
import time
def execute_with_retry(api_func, max_retries=3, backoff_factor=2):
for attempt in range(max_retries):
try:
return api_func()
except Exception as e:
if "429" in str(e) and attempt < max_retries - 1:
wait_time = backoff_factor ** attempt
print(f"Rate limited. Retrying in {wait_time} seconds...")
time.sleep(wait_time)
else:
raise
Error: Empty Buckets
- Cause: No conversations match the criteria within the time frame.
- Fix: Verify the time range is recent enough to contain data. Check that the queues in your organization have had conversations in the selected period. Ensure the service account has visibility into the queues.