Extracting CSAT Survey Responses via the Genesys Cloud Quality API
What You Will Build
- This tutorial demonstrates how to retrieve Customer Satisfaction (CSAT) survey responses linked to specific conversation interactions using the Genesys Cloud Quality API.
- The code utilizes the
/api/v2/quality/evaluationsand/api/v2/quality/surveyresponsesendpoints to correlate survey data with interaction metadata. - The implementation is provided in Python using the
genesys-cloud-sdk-pythonlibrary.
Prerequisites
- OAuth Client Type: Public or Confidential Client.
- Required Scopes:
quality:evaluation:read(To read evaluation details if cross-referencing)quality:response:read(To read survey responses)analytics:conversation:read(Optional, if you need to fetch the raw interaction transcript)
- SDK Version:
genesys-cloud-sdk-pythonversion 150.0.0 or higher. - Language/Runtime: Python 3.8+.
- External Dependencies:
genesys-cloud-sdkpython-dotenv(for secure credential management)
Authentication Setup
Genesys Cloud uses OAuth 2.0 for authentication. For server-to-server integrations, the Client Credentials grant type is standard. The SDK handles token acquisition and refresh automatically when initialized correctly.
Create a .env file in your project root with the following variables:
GENESYS_CLOUD_REGION=us-east-1
GENESYS_CLOUD_CLIENT_ID=your_client_id_here
GENESYS_CLOUD_CLIENT_SECRET=your_client_secret_here
Install the SDK and dependencies:
pip install genesys-cloud-sdk python-dotenv
Initialize the API client in your script. This setup ensures that all subsequent API calls use a valid access token.
import os
from dotenv import load_dotenv
from purecloud_platform_client import PlatformClient, Configuration
load_dotenv()
def get_purecloud_client():
"""
Initializes and returns a configured PureCloud Platform Client.
Handles region routing and OAuth client credentials.
"""
region = os.getenv("GENESYS_CLOUD_REGION", "us-east-1")
client_id = os.getenv("GENESYS_CLOUD_CLIENT_ID")
client_secret = os.getenv("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 in .env")
# Configure the client with OAuth credentials
configuration = Configuration(client_id=client_id, client_secret=client_secret, region=region)
# Initialize the platform client
client = PlatformClient(configuration)
return client
Implementation
Step 1: Querying Survey Responses with Interaction Filters
The Quality API does not provide a single endpoint that returns “Survey Response + Interaction Transcript” in one call. Instead, you must query the SurveyResponse entity and filter by conversationId or evaluationId.
To extract responses tied to specific interactions, you typically start with the Interaction ID (Conversation ID). The SurveyResponse object contains a conversationId field.
Here is how to fetch all survey responses for a specific date range and filter them for a specific conversation.
from purecloud_platform_client.rest import ApiException
from purecloud_platform_client.models import SurveyResponseQuery
def get_survey_responses_by_conversation(client, conversation_id, start_time, end_time):
"""
Fetches survey responses for a specific conversation ID within a time window.
Args:
client: The initialized PureCloud PlatformClient.
conversation_id (str): The ID of the conversation/interaction.
start_time (str): ISO 8601 start time (e.g., "2023-10-01T00:00:00Z").
end_time (str): ISO 8601 end time (e.g., "2023-10-31T23:59:59Z").
Returns:
list: A list of SurveyResponse objects.
"""
quality_api = client.quality_api
# Construct the query filter
# The SurveyResponseQuery allows filtering by conversationId
query = SurveyResponseQuery(
filter={
"conversationId": conversation_id
},
startTime=start_time,
endTime=end_time,
pageSize=50,
pageNumber=1
)
try:
# POST /api/v2/quality/surveyresponses/query
result = quality_api.post_quality_surveyresponses_query(body=query)
if not result.entities:
print(f"No survey responses found for conversation {conversation_id}.")
return []
return result.entities
except ApiException as e:
if e.status == 401:
print("Authentication error: Check Client ID and Secret.")
elif e.status == 403:
print("Permission error: Ensure 'quality:response:read' scope is granted.")
elif e.status == 429:
print("Rate limit exceeded. Implement exponential backoff.")
else:
print(f"API Error: {e.status} - {e.reason}")
raise
Step 2: Handling Pagination for Bulk Extraction
If you are extracting data for a high-volume queue or organization, a single query may not return all results. The SurveyResponseQuery supports pagination via pageNumber and pageSize. You must loop through pages until the nextPage link is null or the entity count is zero.
def get_all_survey_responses_for_date_range(client, start_time, end_time, max_pages=10):
"""
Paginates through all survey responses in a date range.
"""
quality_api = client.quality_api
all_responses = []
page = 1
page_size = 100
while page <= max_pages:
query = SurveyResponseQuery(
startTime=start_time,
endTime=end_time,
pageSize=page_size,
pageNumber=page
)
try:
result = quality_api.post_quality_surveyresponses_query(body=query)
if not result.entities:
break # No more results
all_responses.extend(result.entities)
# Check if there is a next page
if not result.next_page:
break
page += 1
except ApiException as e:
print(f"Error fetching page {page}: {e.reason}")
break
return all_responses
Step 3: Correlating Survey Data with Interaction Metadata
A raw SurveyResponse object contains the score, comments, and timestamp, but it lacks context (e.g., which agent took the call, what channel was used). To build a useful report, you must map the SurveyResponse to the Evaluation or directly to the Conversation details.
The most direct link is via the evaluationId found in the SurveyResponse. If the survey was part of a Quality Evaluation, you can fetch the Evaluation details to get the agentId and conversationId. If it was a standalone survey, you rely on the conversationId directly.
Here is a function that enriches the survey data by fetching the associated Evaluation details to identify the agent.
def enrich_survey_responses_with_agent_data(client, survey_responses):
"""
Takes a list of SurveyResponse objects and fetches associated Evaluation details
to retrieve Agent information.
Args:
client: The initialized PureCloud PlatformClient.
survey_responses (list): List of SurveyResponse objects from Step 1.
Returns:
list: List of dictionaries containing survey data and agent name.
"""
quality_api = client.quality_api
enriched_data = []
# Cache evaluations to avoid redundant API calls if multiple surveys share an evaluation
evaluation_cache = {}
for response in survey_responses:
survey_data = {
"conversationId": response.conversation_id,
"surveyId": response.survey_id,
"score": response.score,
"comments": response.comments,
"completedDate": response.completed_date,
"agentName": "Unknown",
"channel": response.channel_type
}
# If the response is tied to an evaluation, fetch agent details
if response.evaluation_id:
eval_id = response.evaluation_id
if eval_id not in evaluation_cache:
try:
# GET /api/v2/quality/evaluations/{evaluationId}
eval_details = quality_api.get_quality_evaluation(eval_id)
evaluation_cache[eval_id] = eval_details
except ApiException as e:
print(f"Failed to fetch evaluation {eval_id}: {e.reason}")
evaluation_cache[eval_id] = None
eval_details = evaluation_cache[eval_id]
if eval_details and eval_details.agent:
survey_data["agentName"] = eval_details.agent.name
survey_data["agentId"] = eval_details.agent.id
else:
# Fallback: If no evaluation, try to get agent from conversation history
# This requires a separate Analytics call, which is expensive.
# For this tutorial, we mark it as unknown.
survey_data["agentName"] = "No Evaluation Linked"
enriched_data.append(survey_data)
return enriched_data
Complete Working Example
This script combines the previous steps into a single executable module. It authenticates, queries for survey responses in the last 24 hours, enriches them with agent data, and prints the result.
import os
import json
from datetime import datetime, timedelta
from dotenv import load_dotenv
from purecloud_platform_client import PlatformClient, Configuration
from purecloud_platform_client.rest import ApiException
from purecloud_platform_client.models import SurveyResponseQuery
# Load environment variables
load_dotenv()
def get_purecloud_client():
region = os.getenv("GENESYS_CLOUD_REGION", "us-east-1")
client_id = os.getenv("GENESYS_CLOUD_CLIENT_ID")
client_secret = os.getenv("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 in .env")
configuration = Configuration(client_id=client_id, client_secret=client_secret, region=region)
return PlatformClient(configuration)
def get_survey_responses(client, conversation_id, start_time, end_time):
quality_api = client.quality_api
query = SurveyResponseQuery(
filter={"conversationId": conversation_id},
startTime=start_time,
endTime=end_time,
pageSize=50,
pageNumber=1
)
try:
result = quality_api.post_quality_surveyresponses_query(body=query)
return result.entities if result.entities else []
except ApiException as e:
print(f"Error fetching surveys: {e.status} - {e.reason}")
return []
def enrich_survey_responses(client, survey_responses):
quality_api = client.quality_api
enriched_data = []
evaluation_cache = {}
for response in survey_responses:
survey_data = {
"conversationId": response.conversation_id,
"surveyId": response.survey_id,
"score": response.score,
"comments": response.comments,
"completedDate": response.completed_date,
"agentName": "Unknown",
"channel": response.channel_type
}
if response.evaluation_id:
eval_id = response.evaluation_id
if eval_id not in evaluation_cache:
try:
eval_details = quality_api.get_quality_evaluation(eval_id)
evaluation_cache[eval_id] = eval_details
except ApiException:
evaluation_cache[eval_id] = None
eval_details = evaluation_cache[eval_id]
if eval_details and eval_details.agent:
survey_data["agentName"] = eval_details.agent.name
survey_data["agentId"] = eval_details.agent.id
enriched_data.append(survey_data)
return enriched_data
def main():
try:
# 1. Initialize Client
client = get_purecloud_client()
print("Connected to Genesys Cloud.")
# 2. Define Time Range (Last 24 Hours)
end_time = datetime.utcnow()
start_time = end_time - timedelta(hours=24)
start_iso = start_time.strftime("%Y-%m-%dT%H:%M:%SZ")
end_iso = end_time.strftime("%Y-%m-%dT%H:%M:%SZ")
# 3. Fetch Survey Responses for a specific Conversation
# Replace this ID with a real conversation ID from your environment
target_conversation_id = "your-conversation-id-here"
if target_conversation_id == "your-conversation-id-here":
print("Please replace 'your-conversation-id-here' with a valid Conversation ID.")
return
print(f"Fetching surveys for conversation {target_conversation_id} between {start_iso} and {end_iso}")
survey_responses = get_survey_responses(client, target_conversation_id, start_iso, end_iso)
if not survey_responses:
print("No survey responses found for this conversation in the specified timeframe.")
return
# 4. Enrich Data
enriched_surveys = enrich_survey_responses(client, survey_responses)
# 5. Output Results
print("\n--- CSAT Survey Results ---")
for survey in enriched_surveys:
print(json.dumps(survey, indent=2, default=str))
except Exception as e:
print(f"Fatal Error: {str(e)}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden (quality:response:read)
What causes it:
The OAuth token associated with your Client ID does not have the quality:response:read scope. This often happens if the client was created in the Admin Console without explicitly checking the Quality permissions, or if the client is a “Public” client that requires user consent for specific scopes.
How to fix it:
- Log in to the Genesys Cloud Admin Console.
- Navigate to Organization > API Clients.
- Select your Client ID.
- Edit the Scopes.
- Ensure
quality:response:readis checked. - Save changes. Note that existing tokens may need to be refreshed.
Error: 400 Bad Request (Invalid Filter)
What causes it:
The SurveyResponseQuery filter syntax is incorrect. The Genesys Cloud API is strict about JSON structure. Passing conversationId as a string instead of an object, or using the wrong field name (e.g., conversation_id instead of conversationId in the raw JSON payload if not using the SDK) will cause this.
How to fix it:
When using the Python SDK, the SurveyResponseQuery model handles the serialization. Ensure you are passing a dictionary to the filter parameter correctly:
# Correct
filter={"conversationId": "12345-abcde"}
# Incorrect (SDK will reject or serialize poorly)
filter="conversationId=12345-abcde"
Error: 429 Too Many Requests
What causes it:
You are polling the Quality API too frequently. The Quality API has lower rate limits compared to Analytics endpoints. Rapid pagination loops or concurrent requests from multiple threads can trigger this.
How to fix it:
Implement exponential backoff. In the get_all_survey_responses_for_date_range function, add a retry mechanism:
import time
def safe_api_call(func, *args, retries=3):
for attempt in range(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")
Error: Empty Entities List
What causes it:
The conversation ID provided does not have an associated survey response, or the survey response falls outside the startTime and endTime window. Note that the completedDate of the survey is what is filtered, not the conversationId creation time.
How to fix it:
- Verify the
conversationIdexists in the Analytics API. - Widen the
startTimeandendTimewindow. - Check if the survey was actually sent and completed. If the survey was sent but not completed, it will not appear in
surveyresponses. It may appear insurveys(sent surveys) but notresponses.