Retrieving the Full Conversation Transcript (Voice-to-Text) via the Genesys Cloud Speech and Text Analytics API
What You Will Build
- This tutorial demonstrates how to retrieve the complete, searchable text transcript of a voice conversation from Genesys Cloud.
- The solution utilizes the Genesys Cloud Analytics API (
/api/v2/analytics/conversations/details/query) and the Speech Analytics API (/api/v2/analytics/speech/conversations/details/query). - The code is implemented in Python using the
purecloudplatformclientv2SDK, providing a robust, type-safe interface for production environments.
Prerequisites
- OAuth Client Type: Service Account or Client Credentials flow.
- Required Scopes:
analytics:conversation:view(to query conversation metadata)speech:analytics:view(to access speech analytics data and transcripts)
- SDK Version:
purecloudplatformclientv2version 150.0.0 or higher. - Language/Runtime: Python 3.8+.
- External Dependencies:
purecloudplatformclientv2requests(used internally by the SDK, but good to have installed)
Authentication Setup
The Genesys Cloud SDK handles the OAuth2 Client Credentials flow automatically once initialized. You must configure the client with your organization’s environment, client ID, and client secret. The SDK manages token caching and automatic refresh, so you do not need to manually handle token expiration in your application logic.
from purecloudplatformclientv2 import (
Configuration,
ApiClient,
AnalyticsApi,
SpeechAnalyticsApi
)
import os
def get_auth_configuration() -> Configuration:
"""
Initializes the Genesys Cloud configuration with OAuth credentials.
"""
config = Configuration(
host="https://api.mypurecloud.com", # Replace with your environment: api.us.genesyscloud.com, etc.
client_id=os.getenv("GENESYS_CLIENT_ID"),
client_secret=os.getenv("GENESYS_CLIENT_SECRET")
)
return config
Implementation
Step 1: Querying the Conversation ID
Speech transcripts are not retrieved by a generic “conversation ID” alone in a single step without context. The most reliable way to fetch a specific transcript is to first identify the conversation using the Analytics API. This allows you to filter by participant ID, start/end time, or conversation ID if you already have it.
For this tutorial, we assume you have a known conversation_id. If you do not, you would use AnalyticsApi().post_analytics_conversations_details_query with a body containing groupBy: ['conversation'] and a dateRange filter.
Endpoint: POST /api/v2/analytics/conversations/details/query
Scope: analytics:conversation:view
from purecloudplatformclientv2.models import (
PostAnalyticsConversationsDetailsQueryRequestBody,
DateRange,
ConversationFilter
)
def find_conversation_id(api_client: ApiClient, target_conv_id: str) -> str:
"""
Verifies the conversation exists and returns the ID.
In a real scenario, you might search by participant_id or date_range.
"""
analytics_api = AnalyticsApi(api_client)
# Define the date range for the last 30 days
date_range = DateRange(
start="2023-01-01T00:00:00.000Z",
end="2023-12-31T23:59:59.999Z"
)
# Filter for the specific conversation ID
conversation_filter = ConversationFilter(
ids=[target_conv_id]
)
body = PostAnalyticsConversationsDetailsQueryRequestBody(
group_by=["conversation"],
date_range=date_range,
filters=conversation_filter
)
try:
response = analytics_api.post_analytics_conversations_details_query(body=body)
if not response.entities or len(response.entities) == 0:
raise ValueError(f"Conversation ID {target_conv_id} not found in analytics.")
return response.entities[0].id
except Exception as e:
print(f"Error querying analytics: {e}")
raise
Step 2: Retrieving the Speech Analytics Transcript
Once the conversation ID is confirmed, you query the Speech Analytics API. This endpoint returns the transcription data, including the text, speaker labels, and timestamps.
Endpoint: POST /api/v2/analytics/speech/conversations/details/query
Scope: speech:analytics:view
The key parameter here is the groupBy setting. To get the full transcript, you must group by conversation. The response body will contain a transcript field within the entities array.
from purecloudplatformclientv2.models import (
PostAnalyticsSpeechConversationsDetailsQueryRequestBody,
SpeechConversationFilter
)
def get_speech_transcript(api_client: ApiClient, conversation_id: str) -> dict:
"""
Retrieves the speech analytics transcript for a specific conversation.
"""
speech_api = SpeechAnalyticsApi(api_client)
# Define the filter for the specific conversation
speech_filter = SpeechConversationFilter(
ids=[conversation_id]
)
# Body must include groupBy: ['conversation'] to get transcript details
body = PostAnalyticsSpeechConversationsDetailsQueryRequestBody(
group_by=["conversation"],
filters=speech_filter
)
try:
response = speech_api.post_analytics_speech_conversations_details_query(body=body)
if not response.entities or len(response.entities) == 0:
raise ValueError(f"No speech analytics data found for conversation {conversation_id}.")
return response.entities[0]
except Exception as e:
print(f"Error retrieving speech transcript: {e}")
raise
Step 3: Processing the Transcript Data
The response from the Speech Analytics API is structured. The transcript field is an array of segments. Each segment contains the text, speaker (if identified), and offset (timestamp in milliseconds). You must iterate through these segments to reconstruct the full readable transcript.
Note: If speech analytics is not enabled for the specific user or queue, the transcript field may be null or empty. Always check for this edge case.
def format_transcript(speech_entity: object) -> str:
"""
Formats the raw speech analytics entity into a human-readable string.
"""
transcript_data = speech_entity.transcript
if not transcript_data or len(transcript_data) == 0:
return "No transcript available for this conversation."
formatted_lines = []
for segment in transcript_data:
# Speaker can be 'Agent', 'Customer', or 'System'
speaker = segment.speaker if segment.speaker else "Unknown"
text = segment.text if segment.text else ""
# Clean up whitespace
text = text.strip()
if text:
formatted_lines.append(f"[{speaker}]: {text}")
return "\n".join(formatted_lines)
Complete Working Example
This script combines all steps into a single runnable module. It initializes the client, verifies the conversation, fetches the speech analytics data, and prints the formatted transcript.
import os
import sys
from purecloudplatformclientv2 import (
Configuration,
ApiClient,
AnalyticsApi,
SpeechAnalyticsApi,
PostAnalyticsConversationsDetailsQueryRequestBody,
DateRange,
ConversationFilter,
PostAnalyticsSpeechConversationsDetailsQueryRequestBody,
SpeechConversationFilter
)
class GenesysTranscriptRetriever:
def __init__(self, client_id: str, client_secret: str, environment: str = "api.mypurecloud.com"):
self.config = Configuration(
host=f"https://{environment}",
client_id=client_id,
client_secret=client_secret
)
self.api_client = ApiClient(self.config)
self.analytics_api = AnalyticsApi(self.api_client)
self.speech_api = SpeechAnalyticsApi(self.api_client)
def get_conversation_transcript(self, conversation_id: str, date_range_start: str, date_range_end: str) -> str:
"""
Main method to retrieve and format the transcript for a given conversation ID.
"""
# Step 1: Validate Conversation Exists
print(f"Validating conversation ID: {conversation_id}...")
try:
analytics_body = PostAnalyticsConversationsDetailsQueryRequestBody(
group_by=["conversation"],
date_range=DateRange(start=date_range_start, end=date_range_end),
filters=ConversationFilter(ids=[conversation_id])
)
analytics_response = self.analytics_api.post_analytics_conversations_details_query(body=analytics_body)
if not analytics_response.entities:
return f"Error: Conversation ID {conversation_id} not found in analytics."
print(f"Conversation found: {analytics_response.entities[0].id}")
except Exception as e:
return f"Error validating conversation: {str(e)}"
# Step 2: Retrieve Speech Analytics Data
print("Fetching speech analytics transcript...")
try:
speech_body = PostAnalyticsSpeechConversationsDetailsQueryRequestBody(
group_by=["conversation"],
filters=SpeechConversationFilter(ids=[conversation_id])
)
speech_response = self.speech_api.post_analytics_speech_conversations_details_query(body=speech_body)
if not speech_response.entities:
return "Error: No speech analytics data found for this conversation."
speech_entity = speech_response.entities[0]
except Exception as e:
return f"Error fetching speech analytics: {str(e)}"
# Step 3: Format the Transcript
try:
return self._format_transcript(speech_entity)
except Exception as e:
return f"Error formatting transcript: {str(e)}"
def _format_transcript(self, speech_entity) -> str:
"""
Helper method to format the transcript segments.
"""
transcript_data = speech_entity.transcript
if not transcript_data or len(transcript_data) == 0:
return "Transcript field is empty or null. Speech analytics may not be enabled for this conversation."
lines = []
for segment in transcript_data:
speaker = segment.speaker if segment.speaker else "Unknown"
text = segment.text if segment.text else ""
if text.strip():
lines.append(f"[{speaker}]: {text.strip()}")
return "\n".join(lines)
if __name__ == "__main__":
# Configuration
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
ENVIRONMENT = os.getenv("GENESYS_ENVIRONMENT", "api.mypurecloud.com")
CONVERSATION_ID = os.getenv("GENESYS_CONVERSATION_ID", "your-actual-conversation-id-here")
# Date Range for Analytics Query (Last 30 Days)
# Note: Analytics queries require a date range. Adjust as needed.
DATE_START = "2023-01-01T00:00:00.000Z"
DATE_END = "2023-12-31T23:59:59.999Z"
if not CLIENT_ID or not CLIENT_SECRET:
print("Error: GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
sys.exit(1)
retriever = GenesysTranscriptRetriever(CLIENT_ID, CLIENT_SECRET, ENVIRONMENT)
print("Starting Transcript Retrieval...")
transcript = retriever.get_conversation_transcript(CONVERSATION_ID, DATE_START, DATE_END)
print("\n--- TRANSCRIPT ---")
print(transcript)
print("------------------")
Common Errors & Debugging
Error: 401 Unauthorized
- Cause: The OAuth token is invalid, expired, or the Client ID/Secret is incorrect.
- Fix: Verify the environment variables
GENESYS_CLIENT_IDandGENESYS_CLIENT_SECRETmatch the service account in Genesys Cloud. Ensure the service account is active. - Code Fix: The SDK automatically retries token acquisition. If the error persists, check the service account’s permissions.
Error: 403 Forbidden
- Cause: The service account lacks the required OAuth scopes.
- Fix: Ensure the service account has the
speech:analytics:viewandanalytics:conversation:viewscopes assigned in the Genesys Cloud Admin Console under Security > OAuth Credentials. - Debugging: Check the error message body. It will explicitly state which scope is missing.
Error: 429 Too Many Requests
- Cause: You have exceeded the rate limit for the Analytics API.
- Fix: Implement exponential backoff. The Genesys Cloud SDK does not automatically retry 429s for all endpoints, so you must handle this in your loop if querying multiple conversations.
- Code Fix:
import time # Inside your retry loop except Exception as e: if "429" in str(e): retry_after = int(e.headers.get("Retry-After", 5)) time.sleep(retry_after) # Retry logic here else: raise
Error: Transcript is Null or Empty
- Cause: Speech Analytics is not enabled for the specific queue, user, or conversation. Or the conversation is too recent and has not yet been processed.
- Fix:
- Verify Speech Analytics is enabled in Admin > Speech Analytics > Settings.
- Check if the conversation status is
completed. Transcripts are only available after the conversation ends and processing completes. - Wait 5-15 minutes after a conversation ends before querying, as transcription is asynchronous.
Error: Conversation ID Not Found in Analytics
- Cause: The
date_rangein the analytics query does not cover the time of the conversation. - Fix: Ensure the
DateRangeobject inPostAnalyticsConversationsDetailsQueryRequestBodyincludes the start and end times of the conversation. Analytics data is partitioned by date.