Programmatically Start and Stop Call Recordings via the Genesys Cloud CX Recording API
What You Will Build
- A Python script that initiates a recording session for an active conversation and stops it on demand.
- This tutorial uses the Genesys Cloud CX Platform API v2 and the
genesys-cloud-purecloud-platform-clientSDK. - The implementation is demonstrated in Python 3.9+ using the official SDK.
Prerequisites
- OAuth Client Type: Confidential Client (Client Credentials Grant) or JWT Grant.
- Required Scopes:
recording:session:startrecording:session:stoprecording:session:view(to verify status)conversation:call:view(to retrieve active conversation IDs if not passed in)
- SDK Version:
genesys-cloud-purecloud-platform-client>= 138.0.0 - Runtime: Python 3.9 or higher.
- Dependencies:
pip install genesys-cloud-purecloud-platform-client python-dotenv
Authentication Setup
Genesys Cloud CX uses OAuth 2.0 for authentication. For server-side integrations like automated recording controls, the Client Credentials Grant is the standard approach. It requires a Client ID and Client Secret generated from the Admin Console under Security > OAuth Clients.
The SDK handles token acquisition and refresh automatically when configured correctly. You must provide the environment variables PURECLOUD_CLIENT_ID and PURECLOUD_CLIENT_SECRET.
import os
from purecloudplatformclientv2 import Configuration, ApiClient
from purecloudplatformclientv2.auth import OAuthClientCredentials
def get_auth_api_client() -> ApiClient:
"""
Initializes and returns an authenticated ApiClient instance.
"""
# Load environment variables
client_id = os.getenv("PURECLOUD_CLIENT_ID")
client_secret = os.getenv("PURECLOUD_CLIENT_SECRET")
environment = os.getenv("PURECLOUD_ENVIRONMENT", "mypurecloud.com")
if not client_id or not client_secret:
raise ValueError("PURECLOUD_CLIENT_ID and PURECLOUD_CLIENT_SECRET must be set.")
# Create configuration
config = Configuration(
host=f"https://{environment}",
access_token=None, # Will be set by auth
client_id=client_id,
client_secret=client_secret
)
# Create API client
api_client = ApiClient(configuration=config)
# Authenticate using Client Credentials
# This sets the access_token in the config automatically
auth = OAuthClientCredentials(
client_id=client_id,
client_secret=client_secret,
api_client=api_client
)
# Trigger authentication
api_client.default_headers['Authorization'] = f"Bearer {auth.get_access_token()}"
return api_client
Note on Scopes: When generating the OAuth Client in the Admin Console, ensure the specific recording scopes listed in Prerequisites are checked. If a scope is missing, the API will return a 403 Forbidden error with a message indicating the required scope.
Implementation
The Recording API in Genesys Cloud operates on the concept of Recording Sessions. A session is not the audio file itself, but the logical container that defines when and how a recording is captured. To record a call, you must start a session linked to a specific Conversation ID. To stop it, you stop that session.
Step 1: Identify the Active Conversation
You cannot start a recording without a conversationId. In a production system, this ID is typically passed from your CTI softphone or obtained via the Conversations API. For this tutorial, we assume you have the conversationId from an active inbound or outbound call.
If you need to find the ID programmatically, you would query the Conversations API. However, to keep this focused on recordings, we will define a helper function that accepts the ID directly.
Required Scope: conversation:call:view (if querying), recording:session:start (if starting).
from purecloudplatformclientv2 import ConversationsApi, RecordingApi
from purecloudplatformclientv2.models import StartRecordingSessionRequest
def find_active_call(conversation_id: str, api_client: ApiClient) -> str:
"""
Verifies the conversation exists and is active.
In production, you might query /api/v2/conversations/active to find this ID.
"""
conversations_api = ConversationsApi(api_client)
try:
# Get conversation details
conversation = conversations_api.get_conversation_call(conversation_id)
if conversation.media_type != "call":
raise ValueError(f"Conversation {conversation_id} is not a voice call.")
if conversation.state != "active":
raise ValueError(f"Conversation {conversation_id} is not active. Current state: {conversation.state}")
return conversation_id
except Exception as e:
# Handle 404 (Not Found) or 403 (Forbidden)
print(f"Error verifying conversation: {e}")
raise
Step 2: Start the Recording Session
To start recording, you send a POST request to /api/v2/recording/sessions. The body must contain the conversationId and optionally a reasonCode. If no reason code is provided, the default recording reason configured in the Genesys Cloud tenant is used.
API Endpoint: POST /api/v2/recording/sessions
SDK Method: RecordingApi.post_recording_sessions
def start_recording(conversation_id: str, api_client: ApiClient, reason_code: str = None) -> dict:
"""
Starts a recording session for the given conversation.
Args:
conversation_id (str): The ID of the active conversation.
api_client (ApiClient): Authenticated API client.
reason_code (str, optional): The ID of the reason code for the recording.
Returns:
dict: The response containing the session ID and status.
"""
recording_api = RecordingApi(api_client)
# Construct the request body
request_body = StartRecordingSessionRequest(
conversation_id=conversation_id,
reason_code=reason_code
)
try:
# Execute the start recording call
# The SDK returns a RecordingSession object
response = recording_api.post_recording_sessions(body=request_body)
print(f"Recording started. Session ID: {response.id}")
print(f"Status: {response.status}")
return {
"session_id": response.id,
"status": response.status,
"conversation_id": response.conversation_id
}
except Exception as e:
# Handle API errors
# 400: Invalid conversation ID or conversation not recordable
# 403: Missing scopes
# 409: Recording already in progress for this conversation
print(f"Failed to start recording: {e}")
raise
Critical Edge Case: If a recording is already active for the conversation, the API returns a 409 Conflict. You should check for this status code and handle it gracefully, perhaps by ignoring the start command or notifying the user.
Step 3: Stop the Recording Session
Stopping a recording is decoupled from the conversation state. You can stop a recording while the call is still active. The API endpoint requires the sessionId obtained from the start response.
API Endpoint: POST /api/v2/recording/sessions/{sessionId}/stop
SDK Method: RecordingApi.post_recording_sessions_session_id_stop
def stop_recording(session_id: str, api_client: ApiClient) -> dict:
"""
Stops an active recording session.
Args:
session_id (str): The ID of the recording session to stop.
api_client (ApiClient): Authenticated API client.
Returns:
dict: The response containing the final session status.
"""
recording_api = RecordingApi(api_client)
try:
# Execute the stop recording call
# This endpoint typically returns a 200 OK with the updated session object
response = recording_api.post_recording_sessions_session_id_stop(session_id)
print(f"Recording stopped. Session ID: {response.id}")
print(f"Final Status: {response.status}")
return {
"session_id": response.id,
"status": response.status
}
except Exception as e:
# 404: Session not found
# 409: Session is not active (already stopped or failed)
print(f"Failed to stop recording: {e}")
raise
Step 4: Verify Recording Status (Optional but Recommended)
Before stopping a recording, it is good practice to check its current status. The GET /api/v2/recording/sessions/{sessionId} endpoint allows you to retrieve the session details.
API Endpoint: GET /api/v2/recording/sessions/{sessionId}
SDK Method: RecordingApi.get_recording_sessions_session_id
def get_recording_status(session_id: str, api_client: ApiClient) -> dict:
"""
Retrieves the current status of a recording session.
"""
recording_api = RecordingApi(api_client)
try:
response = recording_api.get_recording_sessions_session_id(session_id)
return {
"session_id": response.id,
"status": response.status,
"conversation_id": response.conversation_id,
"start_time": response.start_time.isoformat() if response.start_time else None,
"end_time": response.end_time.isoformat() if response.end_time else None
}
except Exception as e:
print(f"Failed to get recording status: {e}")
raise
Complete Working Example
Below is the complete, runnable Python script. It demonstrates the lifecycle: Start → Verify → Stop.
Prerequisites:
- Set environment variables:
PURECLOUD_CLIENT_ID,PURECLOUD_CLIENT_SECRET. - Have an active Genesys Cloud conversation ID ready.
import os
import time
from purecloudplatformclientv2 import Configuration, ApiClient, ConversationsApi, RecordingApi
from purecloudplatformclientv2.auth import OAuthClientCredentials
from purecloudplatformclientv2.models import StartRecordingSessionRequest
def get_auth_api_client() -> ApiClient:
client_id = os.getenv("PURECLOUD_CLIENT_ID")
client_secret = os.getenv("PURECLOUD_CLIENT_SECRET")
environment = os.getenv("PURECLOUD_ENVIRONMENT", "mypurecloud.com")
if not client_id or not client_secret:
raise ValueError("PURECLOUD_CLIENT_ID and PURECLOUD_CLIENT_SECRET must be set.")
config = Configuration(
host=f"https://{environment}",
client_id=client_id,
client_secret=client_secret
)
api_client = ApiClient(configuration=config)
# Authenticate
auth = OAuthClientCredentials(
client_id=client_id,
client_secret=client_secret,
api_client=api_client
)
api_client.default_headers['Authorization'] = f"Bearer {auth.get_access_token()}"
return api_client
def start_recording(conversation_id: str, api_client: ApiClient) -> str:
recording_api = RecordingApi(api_client)
request_body = StartRecordingSessionRequest(conversation_id=conversation_id)
try:
response = recording_api.post_recording_sessions(body=request_body)
print(f"Started recording for conversation {conversation_id}. Session: {response.id}")
return response.id
except Exception as e:
print(f"Error starting recording: {e}")
raise
def stop_recording(session_id: str, api_client: ApiClient) -> None:
recording_api = RecordingApi(api_client)
try:
response = recording_api.post_recording_sessions_session_id_stop(session_id)
print(f"Stopped recording session {session_id}. Final status: {response.status}")
except Exception as e:
print(f"Error stopping recording: {e}")
raise
def main():
# Configuration
# Replace this with a real active conversation ID from your Genesys Cloud instance
TARGET_CONVERSATION_ID = os.getenv("TARGET_CONVERSATION_ID")
if not TARGET_CONVERSATION_ID:
print("Please set TARGET_CONVERSATION_ID environment variable.")
return
try:
# 1. Initialize Client
print("Initializing API Client...")
api_client = get_auth_api_client()
# 2. Start Recording
print("Starting Recording...")
session_id = start_recording(TARGET_CONVERSATION_ID, api_client)
# 3. Wait for a few seconds to simulate recording duration
print("Recording in progress... waiting 5 seconds.")
time.sleep(5)
# 4. Stop Recording
print("Stopping Recording...")
stop_recording(session_id, api_client)
print("Process completed successfully.")
except Exception as e:
print(f"Fatal error: {e}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden - Insufficient Scopes
Cause: The OAuth client used does not have the recording:session:start or recording:session:stop scopes enabled.
Fix:
- Go to Genesys Cloud Admin Console.
- Navigate to Security > OAuth Clients.
- Select your client.
- Edit the Scopes tab.
- Check
recording:session:start,recording:session:stop, andrecording:session:view. - Save and regenerate the access token.
Error: 409 Conflict - Recording Already Active
Cause: You attempted to start a recording for a conversation that is already being recorded. Genesys Cloud does not support multiple simultaneous recording sessions for the same conversation ID.
Fix:
- Check the response status code in your code.
- If
409, do not retry the start command. - Optionally, fetch the existing session ID via
GET /api/v2/recording/sessionsfiltered by conversation ID and use that to stop the recording later.
# Example check for 409
try:
recording_api.post_recording_sessions(body=request_body)
except Exception as e:
if "409" in str(e):
print("Recording is already active for this conversation.")
else:
raise
Error: 400 Bad Request - Conversation Not Found or Invalid
Cause: The conversationId provided is invalid, expired, or refers to a non-voice conversation (e.g., chat or email).
Fix:
- Verify the
conversationIdformat (UUID). - Ensure the conversation is currently in an
activestate. - Ensure the conversation media type is
call. - Use the
ConversationsApi.get_conversation_callmethod to validate before attempting to start recording.
Error: 429 Too Many Requests
Cause: You have exceeded the API rate limits. Genesys Cloud enforces rate limits per client ID.
Fix:
- Implement exponential backoff in your code.
- Check the
Retry-Afterheader in the response. - Avoid polling the recording status in a tight loop. Use webhooks for real-time status updates if possible.
import time
def stop_recording_with_retry(session_id: str, api_client: ApiClient, max_retries: int = 3):
recording_api = RecordingApi(api_client)
for attempt in range(max_retries):
try:
response = recording_api.post_recording_sessions_session_id_stop(session_id)
return response
except Exception as e:
if "429" in str(e) or "Too Many Requests" in str(e):
wait_time = 2 ** attempt
print(f"Rate limited. Waiting {wait_time} seconds...")
time.sleep(wait_time)
else:
raise
raise Exception("Max retries exceeded")