Get Real-Time Queue Metrics via Genesys Cloud Statistics API
What You Will Build
- A Python script that retrieves real-time queue statistics, including the count of waiting interactions and available agents.
- This uses the Genesys Cloud CX Statistics API (
/api/v2/analytics/queues/details/query). - The programming language covered is Python 3.9+ using the
requestslibrary.
Prerequisites
- OAuth Client: A Genesys Cloud OAuth client with the
contactcenter:queue:readscope. - API Version: Genesys Cloud CX API v2.
- Language/Runtime: Python 3.9 or higher.
- External Dependencies:
requests,python-dotenv(for secure credential management).
Install dependencies:
pip install requests python-dotenv
Authentication Setup
Genesys Cloud uses OAuth 2.0 for authentication. For server-to-server integrations, you will use the Client Credentials flow. You must handle token expiration by implementing a refresh mechanism or caching the token until it expires.
Create a .env file in your project root:
GENESYS_CLOUD_SUBDOMAIN=your-subdomain
GENESYS_CLOUD_CLIENT_ID=your-client-id
GENESYS_CLOUD_CLIENT_SECRET=your-client-secret
The following class handles authentication and token caching. It ensures you do not make unnecessary requests to the token endpoint while the token remains valid.
import os
import time
import requests
from dotenv import load_dotenv
load_dotenv()
class GenesysAuth:
def __init__(self):
self.subdomain = os.getenv("GENESYS_CLOUD_SUBDOMAIN")
self.client_id = os.getenv("GENESYS_CLOUD_CLIENT_ID")
self.client_secret = os.getenv("GENESYS_CLOUD_CLIENT_SECRET")
self.token_url = f"https://{self.subdomain}.mypurecloud.com/oauth/token"
self.access_token = None
self.token_expiry = 0
def get_token(self) -> str:
"""
Returns a valid access token. Refreshes if expired or not present.
"""
# Check if token is still valid (subtract 60 seconds for safety buffer)
if self.access_token and time.time() < (self.token_expiry - 60):
return self.access_token
payload = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"scope": "contactcenter:queue:read"
}
try:
response = requests.post(self.token_url, data=payload)
response.raise_for_status()
data = response.json()
self.access_token = data["access_token"]
# expires_in is in seconds
self.token_expiry = time.time() + data["expires_in"]
return self.access_token
except requests.exceptions.RequestException as e:
raise Exception(f"Failed to obtain OAuth token: {e}")
# Initialize authentication
auth = GenesysAuth()
Implementation
Step 1: Construct the Queue Details Query
The Statistics API uses a query-based model for real-time data. You do not fetch data for a single queue ID directly in the URL path. Instead, you send a POST request to /api/v2/analytics/queues/details/query with a JSON body containing the filters.
The critical filter is queueIds. You can also filter by timeFrame if you need historical snapshots, but for real-time data, you omit the time frame or use the default current state.
import json
from datetime import datetime, timedelta
def build_queue_query(queue_ids: list) -> dict:
"""
Constructs the request body for the queue details query.
"""
# Define the view as "default" to get standard real-time metrics
# "realtime" view is also available but "default" is sufficient for most counts
query_body = {
"view": "default",
"groupBy": [],
"select": [
"queueId",
"queueName",
"count",
"waitingCount",
"availableCount",
"interactingCount",
"longestWait",
"avgWait"
],
"filter": {
"type": "and",
"clauses": [
{
"type": "fieldEquals",
"field": "queueId",
"value": queue_ids,
"op": "in"
}
]
}
}
return query_body
Step 2: Execute the API Request
This step sends the query to the Genesys Cloud API. You must handle the 429 Too Many Requests error by implementing exponential backoff. Genesys Cloud enforces rate limits per client ID.
import random
def fetch_realtime_queue_stats(auth: GenesysAuth, queue_ids: list, max_retries: int = 3) -> dict:
"""
Fetches real-time statistics for specified queues.
Implements exponential backoff for 429 errors.
"""
subdomain = auth.subdomain
endpoint = f"https://{subdomain}.mypurecloud.com/api/v2/analytics/queues/details/query"
headers = {
"Authorization": f"Bearer {auth.get_token()}",
"Content-Type": "application/json"
}
payload = build_queue_query(queue_ids)
for attempt in range(max_retries):
try:
response = requests.post(endpoint, json=payload, headers=headers)
# Handle Rate Limiting (429)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
# Add jitter to prevent thundering herd
sleep_time = retry_after + random.uniform(0, 1)
print(f"Rate limited. Retrying in {sleep_time:.2f} seconds...")
time.sleep(sleep_time)
continue
# Handle Authentication Errors
if response.status_code == 401:
raise Exception("Unauthorized. Check OAuth token and scopes.")
# Handle Not Found or Bad Request
if response.status_code in [400, 404]:
raise Exception(f"API Error {response.status_code}: {response.text}")
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Request failed on attempt {attempt + 1}: {e}")
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt) # Exponential backoff for network errors
return {}
Step 3: Process and Interpret Results
The response from the Statistics API contains an array of entities. Each entity represents a queue (or a specific time slice if grouping by time). You must parse this array to extract the metrics for each queue.
Key fields:
waitingCount: The number of interactions currently waiting in the queue.availableCount: The number of agents logged in and available to take interactions.interactingCount: The number of agents currently on a call/chat.count: The total number of agents in the queue (available + interacting + offline).
def parse_queue_stats(api_response: dict) -> list:
"""
Parses the API response into a list of readable queue metrics.
"""
if not api_response.get("entities"):
return []
queue_metrics = []
for entity in api_response["entities"]:
# Extract metrics safely with defaults
waiting = entity.get("waitingCount", 0)
available = entity.get("availableCount", 0)
interacting = entity.get("interactingCount", 0)
total_agents = entity.get("count", 0)
longest_wait_seconds = entity.get("longestWait", 0)
avg_wait_seconds = entity.get("avgWait", 0)
# Convert seconds to minutes for readability
longest_wait_min = round(longest_wait_seconds / 60, 2)
avg_wait_min = round(avg_wait_seconds / 60, 2)
metric = {
"queue_id": entity.get("queueId"),
"queue_name": entity.get("queueName"),
"waiting_count": waiting,
"available_agents": available,
"interacting_agents": interacting,
"total_agents": total_agents,
"longest_wait_minutes": longest_wait_min,
"avg_wait_minutes": avg_wait_min
}
queue_metrics.append(metric)
return queue_metrics
Complete Working Example
This script combines authentication, query construction, API execution, and result parsing. It monitors a list of queue IDs and prints the real-time status.
import os
import time
import requests
import random
import json
from dotenv import load_dotenv
load_dotenv()
class GenesysAuth:
def __init__(self):
self.subdomain = os.getenv("GENESYS_CLOUD_SUBDOMAIN")
self.client_id = os.getenv("GENESYS_CLOUD_CLIENT_ID")
self.client_secret = os.getenv("GENESYS_CLOUD_CLIENT_SECRET")
self.token_url = f"https://{self.subdomain}.mypurecloud.com/oauth/token"
self.access_token = None
self.token_expiry = 0
def get_token(self) -> str:
if self.access_token and time.time() < (self.token_expiry - 60):
return self.access_token
payload = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"scope": "contactcenter:queue:read"
}
try:
response = requests.post(self.token_url, data=payload)
response.raise_for_status()
data = response.json()
self.access_token = data["access_token"]
self.token_expiry = time.time() + data["expires_in"]
return self.access_token
except requests.exceptions.RequestException as e:
raise Exception(f"Failed to obtain OAuth token: {e}")
def build_queue_query(queue_ids: list) -> dict:
return {
"view": "default",
"groupBy": [],
"select": [
"queueId",
"queueName",
"count",
"waitingCount",
"availableCount",
"interactingCount",
"longestWait",
"avgWait"
],
"filter": {
"type": "and",
"clauses": [
{
"type": "fieldEquals",
"field": "queueId",
"value": queue_ids,
"op": "in"
}
]
}
}
def fetch_realtime_queue_stats(auth: GenesysAuth, queue_ids: list, max_retries: int = 3) -> dict:
subdomain = auth.subdomain
endpoint = f"https://{subdomain}.mypurecloud.com/api/v2/analytics/queues/details/query"
headers = {
"Authorization": f"Bearer {auth.get_token()}",
"Content-Type": "application/json"
}
payload = build_queue_query(queue_ids)
for attempt in range(max_retries):
try:
response = requests.post(endpoint, json=payload, headers=headers)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
sleep_time = retry_after + random.uniform(0, 1)
print(f"Rate limited. Retrying in {sleep_time:.2f} seconds...")
time.sleep(sleep_time)
continue
if response.status_code == 401:
raise Exception("Unauthorized. Check OAuth token and scopes.")
if response.status_code in [400, 404]:
raise Exception(f"API Error {response.status_code}: {response.text}")
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Request failed on attempt {attempt + 1}: {e}")
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)
return {}
def parse_queue_stats(api_response: dict) -> list:
if not api_response.get("entities"):
return []
queue_metrics = []
for entity in api_response["entities"]:
metric = {
"queue_id": entity.get("queueId"),
"queue_name": entity.get("queueName"),
"waiting_count": entity.get("waitingCount", 0),
"available_agents": entity.get("availableCount", 0),
"interacting_agents": entity.get("interactingCount", 0),
"total_agents": entity.get("count", 0),
"longest_wait_minutes": round(entity.get("longestWait", 0) / 60, 2),
"avg_wait_minutes": round(entity.get("avgWait", 0) / 60, 2)
}
queue_metrics.append(metric)
return queue_metrics
def main():
# Replace with actual Queue IDs from your Genesys Cloud instance
TARGET_QUEUE_IDS = ["queue-id-1", "queue-id-2"]
if not TARGET_QUEUE_IDS:
print("Please provide valid Queue IDs in TARGET_QUEUE_IDS.")
return
auth = GenesysAuth()
print(f"Fetching real-time stats for {len(TARGET_QUEUE_IDS)} queues...")
try:
raw_response = fetch_realtime_queue_stats(auth, TARGET_QUEUE_IDS)
metrics = parse_queue_stats(raw_response)
if not metrics:
print("No data returned. Check Queue IDs and permissions.")
return
print("\n--- Real-Time Queue Status ---")
for m in metrics:
print(f"Queue: {m['queue_name']}")
print(f" Waiting: {m['waiting_count']}")
print(f" Available Agents: {m['available_agents']}")
print(f" Interacting Agents: {m['interacting_agents']}")
print(f" Total Agents: {m['total_agents']}")
print(f" Avg Wait: {m['avg_wait_minutes']} min")
print(f" Longest Wait: {m['longest_wait_minutes']} min")
print("-" * 30)
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 401 Unauthorized
Cause: The OAuth token is expired, invalid, or the client credentials are incorrect.
Fix:
- Verify
GENESYS_CLOUD_CLIENT_IDandGENESYS_CLOUD_CLIENT_SECRETin your.envfile. - Ensure the OAuth client has the
contactcenter:queue:readscope assigned in the Genesys Cloud Admin Console. - Check that your subdomain matches the one used in the token URL.
Error: 403 Forbidden
Cause: The OAuth client has the correct token but lacks the specific permission to read queue statistics.
Fix:
- Go to Admin > Applications > OAuth Clients.
- Select your client.
- Under “Scopes”, ensure
contactcenter:queue:readis checked. - Save and regenerate the token.
Error: 429 Too Many Requests
Cause: You have exceeded the rate limit for the Statistics API. The limit is typically per client ID.
Fix:
- Implement exponential backoff as shown in the
fetch_realtime_queue_statsfunction. - Respect the
Retry-Afterheader in the response. - Avoid polling too frequently. For real-time dashboards, consider using Websockets (
/api/v2/analytics/conversations/details/queryis not websocket-based, but for pure real-time streaming, look into the Events API or Websockets for interactions). However, for queue stats, polling every 10-30 seconds is usually safe.
Error: Empty Entities Array
Cause: The queueIds filter returned no results.
Fix:
- Verify that the
queueIdsinTARGET_QUEUE_IDSare valid UUIDs from your Genesys Cloud instance. - Ensure the queues exist and are not deleted.
- Check that the OAuth client has access to the specific routing configuration if using specific org permissions.