Programmatically Start and Stop Call Recordings via the Genesys Cloud API
What You Will Build
- This tutorial demonstrates how to programmatically initiate and terminate call recording sessions for active conversations using the Genesys Cloud API.
- The implementation utilizes the Genesys Cloud
analytics/conversationsandapi/v2/conversationsendpoints to manage recording states. - The examples are provided in Python using the
requestslibrary and the officialgenesys-cloud-purecloud-platform-clientSDK.
Prerequisites
- OAuth Client Type: A Genesys Cloud OAuth Client with the following scopes:
conversations:readconversations:writerecording:readrecording:writeanalytics:conversations:read(for retrieving recording metadata)
- SDK Version:
genesys-cloud-purecloud-platform-client>= 170.0.0 (Python) - Runtime: Python 3.9+
- Dependencies:
pip install genesys-cloud-purecloud-platform-clientpip install requests
Authentication Setup
Genesys Cloud uses OAuth 2.0 for authentication. The most robust method for server-to-server integrations is the Client Credentials flow. You must cache the access token and handle expiration gracefully.
The following Python function establishes a secure connection and retrieves a valid access token. It includes basic error handling for invalid credentials or network timeouts.
import os
import time
import requests
from typing import Optional, Dict, Any
# Configuration from environment variables
GENESYS_REGION = os.getenv("GENESYS_REGION", "mypurecloud.com")
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
AUTH_URL = f"https://{GENESYS_REGION}/oauth/token"
def get_access_token() -> str:
"""
Retrieves an OAuth 2.0 access token using the Client Credentials flow.
Raises an exception if authentication fails.
"""
if not CLIENT_ID or not CLIENT_SECRET:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET must be set.")
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
}
try:
response = requests.post(AUTH_URL, headers=headers, data=data, timeout=10)
response.raise_for_status()
token_data = response.json()
return token_data["access_token"]
except requests.exceptions.HTTPError as http_err:
raise Exception(f"Authentication failed: {http_err}") from http_err
except requests.exceptions.RequestException as req_err:
raise Exception(f"Network error during authentication: {req_err}") from req_err
# Example usage:
# token = get_access_token()
Implementation
Genesys Cloud does not have a single “Start Recording” button for active calls in the same way a phone system might. Instead, recording is typically configured at the Routing Strategy or Queue level. However, you can programmatically influence recording behavior by:
- Updating Conversation Attributes: Some recording rules evaluate dynamic attributes. You can push data into the conversation to trigger a rule-based recording start.
- Using the Recording API (Limited): The
recordingAPI is primarily for managing recordings (pause, stop, download) once they are active. You cannot arbitrarily “start” a recording on a silent call unless the call is already in a recorded state due to routing configuration. - Stopping/Pausing Recordings: You can explicitly stop or pause an active recording using the conversation ID.
This tutorial focuses on the actionable parts: Stopping an active recording and Verifying recording status. To “start” a recording programmatically in a dynamic way, we will use a workaround: updating a conversation attribute that triggers a pre-configured recording rule in the Genesys Cloud admin console.
Step 1: Retrieve Active Conversations
To manage a recording, you first need the conversationId. You can retrieve active conversations for a specific user or across the organization.
Endpoint: GET /api/v2/conversations
Scope: conversations:read
import requests
from typing import List, Dict, Any
def get_active_conversations(token: str, user_id: str) -> List[Dict[str, Any]]:
"""
Retrieves a list of active conversations for a specific user.
Args:
token: Valid OAuth access token.
user_id: The ID of the user whose conversations to retrieve.
Returns:
List of conversation objects.
"""
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
url = f"https://{GENESYS_REGION}/api/v2/conversations"
params = {
"userId": user_id,
"active": "true"
}
try:
response = requests.get(url, headers=headers, params=params, timeout=10)
response.raise_for_status()
data = response.json()
return data.get("entities", [])
except requests.exceptions.HTTPError as http_err:
print(f"HTTP Error: {http_err}")
return []
except Exception as e:
print(f"Error retrieving conversations: {e}")
return []
Step 2: Programmatically “Start” Recording via Attribute Update
Since there is no direct POST /start-recording endpoint for arbitrary calls, the industry-standard pattern is to use Dynamic Recording Rules.
- In the Genesys Cloud Admin Console, create a Recording Rule that triggers when a specific attribute (e.g.,
force_record) is set totrue. - Use the API to update this attribute on an active conversation.
Endpoint: PUT /api/v2/conversations/{conversationId}
Scope: conversations:write
def trigger_recording_start(token: str, conversation_id: str) -> bool:
"""
Updates a conversation attribute to trigger a pre-configured recording rule.
This assumes a recording rule exists in Genesys Cloud that listens for
the attribute 'force_record' being set to 'true'.
Args:
token: Valid OAuth access token.
conversation_id: The ID of the active conversation.
Returns:
True if the update was successful, False otherwise.
"""
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
url = f"https://{GENESYS_REGION}/api/v2/conversations/{conversation_id}"
# Payload to update conversation attributes
payload = {
"attributes": {
"force_record": "true"
}
}
try:
response = requests.put(url, headers=headers, json=payload, timeout=10)
response.raise_for_status()
print(f"Successfully updated attributes for conversation {conversation_id}")
return True
except requests.exceptions.HTTPError as http_err:
if http_err.response.status_code == 404:
print(f"Conversation {conversation_id} not found.")
elif http_err.response.status_code == 403:
print(f"Permission denied. Ensure 'conversations:write' scope is granted.")
else:
print(f"HTTP Error: {http_err}")
return False
except Exception as e:
print(f"Error triggering recording: {e}")
return False
Step 3: Stop or Pause an Active Recording
To stop or pause a recording, you interact with the recording resource associated with the conversation. First, you must identify the recording ID.
Endpoint 1: GET /api/v2/conversations/{conversationId}/recordings
Scope: recording:read
Endpoint 2: POST /api/v2/recording/{recordingId}/stop or /pause
Scope: recording:write
def get_active_recordings(token: str, conversation_id: str) -> List[Dict[str, Any]]:
"""
Retrieves active recordings for a specific conversation.
Args:
token: Valid OAuth access token.
conversation_id: The ID of the conversation.
Returns:
List of recording objects.
"""
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
url = f"https://{GENESYS_REGION}/api/v2/conversations/{conversation_id}/recordings"
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
data = response.json()
return data.get("entities", [])
except requests.exceptions.HTTPError as http_err:
print(f"HTTP Error retrieving recordings: {http_err}")
return []
except Exception as e:
print(f"Error retrieving recordings: {e}")
return []
def stop_recording(token: str, recording_id: str) -> bool:
"""
Stops an active recording by its ID.
Args:
token: Valid OAuth access token.
recording_id: The ID of the recording to stop.
Returns:
True if the stop command was accepted, False otherwise.
"""
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
url = f"https://{GENESYS_REGION}/api/v2/recording/{recording_id}/stop"
try:
response = requests.post(url, headers=headers, timeout=10)
response.raise_for_status()
print(f"Successfully stopped recording {recording_id}")
return True
except requests.exceptions.HTTPError as http_err:
if http_err.response.status_code == 404:
print(f"Recording {recording_id} not found or already stopped.")
elif http_err.response.status_code == 409:
print(f"Conflict: Recording {recording_id} is not in a stoppable state.")
else:
print(f"HTTP Error stopping recording: {http_err}")
return False
except Exception as e:
print(f"Error stopping recording: {e}")
return False
def pause_recording(token: str, recording_id: str) -> bool:
"""
Pauses an active recording by its ID.
Args:
token: Valid OAuth access token.
recording_id: The ID of the recording to pause.
Returns:
True if the pause command was accepted, False otherwise.
"""
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
url = f"https://{GENESYS_REGION}/api/v2/recording/{recording_id}/pause"
try:
response = requests.post(url, headers=headers, timeout=10)
response.raise_for_status()
print(f"Successfully paused recording {recording_id}")
return True
except requests.exceptions.HTTPError as http_err:
if http_err.response.status_code == 404:
print(f"Recording {recording_id} not found.")
elif http_err.response.status_code == 409:
print(f"Conflict: Recording {recording_id} is already paused or stopped.")
else:
print(f"HTTP Error pausing recording: {http_err}")
return False
except Exception as e:
print(f"Error pausing recording: {e}")
return False
Complete Working Example
This script integrates all previous steps. It authenticates, finds an active conversation for a user, triggers a recording start via attribute update, waits briefly, and then stops the recording.
import os
import time
import requests
# --- Configuration ---
GENESYS_REGION = os.getenv("GENESYS_REGION", "mypurecloud.com")
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
TARGET_USER_ID = os.getenv("TARGET_USER_ID") # The user ID to monitor
AUTH_URL = f"https://{GENESYS_REGION}/oauth/token"
# --- Authentication ---
def get_access_token() -> str:
if not CLIENT_ID or not CLIENT_SECRET:
raise ValueError("Environment variables CLIENT_ID and CLIENT_SECRET are required.")
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
}
response = requests.post(AUTH_URL, headers=headers, data=data, timeout=10)
response.raise_for_status()
return response.json()["access_token"]
# --- API Helpers ---
def get_active_conversations(token: str, user_id: str):
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
url = f"https://{GENESYS_REGION}/api/v2/conversations"
params = {"userId": user_id, "active": "true"}
response = requests.get(url, headers=headers, params=params, timeout=10)
response.raise_for_status()
return response.json().get("entities", [])
def update_conversation_attribute(token: str, conversation_id: str, attr_name: str, attr_value: str):
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
url = f"https://{GENESYS_REGION}/api/v2/conversations/{conversation_id}"
payload = {"attributes": {attr_name: attr_value}}
response = requests.put(url, headers=headers, json=payload, timeout=10)
response.raise_for_status()
return True
def get_recordings_for_conversation(token: str, conversation_id: str):
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
url = f"https://{GENESYS_REGION}/api/v2/conversations/{conversation_id}/recordings"
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
return response.json().get("entities", [])
def stop_recording(token: str, recording_id: str):
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
url = f"https://{GENESYS_REGION}/api/v2/recording/{recording_id}/stop"
response = requests.post(url, headers=headers, timeout=10)
response.raise_for_status()
return True
# --- Main Workflow ---
def main():
try:
print("1. Authenticating...")
token = get_access_token()
if not TARGET_USER_ID:
print("Error: TARGET_USER_ID not set in environment variables.")
return
print(f"2. Fetching active conversations for user {TARGET_USER_ID}...")
conversations = get_active_conversations(token, TARGET_USER_ID)
if not conversations:
print("No active conversations found.")
return
conversation_id = conversations[0]["id"]
print(f"Found active conversation: {conversation_id}")
print("3. Triggering recording start via attribute update...")
# This assumes a recording rule exists that triggers on 'force_record' == 'true'
update_conversation_attribute(token, conversation_id, "force_record", "true")
# Wait for the recording rule to process and start the recording
print("4. Waiting 5 seconds for recording to initialize...")
time.sleep(5)
print("5. Fetching active recordings...")
recordings = get_recordings_for_conversation(token, conversation_id)
if not recordings:
print("No active recordings found. The rule may not have triggered or the call is not in a recordable state.")
return
recording_id = recordings[0]["id"]
print(f"Found active recording: {recording_id}")
print("6. Stopping recording...")
stop_recording(token, recording_id)
print("Recording stopped successfully.")
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f"Response Body: {e.response.text}")
except Exception as e:
print(f"Unexpected Error: {e}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden
- Cause: The OAuth client lacks the required scopes.
- Fix: Ensure the client has
conversations:writeandrecording:writescopes. Update the client in the Genesys Cloud Admin Console under Security > OAuth Clients.
Error: 404 Not Found
- Cause: The conversation ID or recording ID is invalid or the resource no longer exists.
- Fix: Verify the conversation is still active. Recordings may be deleted or finalized quickly after a call ends. Use the
GET /api/v2/conversations/{id}/recordingsendpoint to confirm the recording exists before attempting to stop it.
Error: 409 Conflict
- Cause: Attempting to stop or pause a recording that is already in that state (e.g., stopping a paused recording without resuming first, or stopping an already stopped recording).
- Fix: Check the
statefield in the recording object. Only call/stopif the state isactiveorpaused. Only call/pauseif the state isactive.
Error: Recording Did Not Start After Attribute Update
- Cause: No recording rule is configured to listen for the attribute change, or the conversation is not in a recordable context (e.g., internal call between agents).
- Fix:
- Go to Admin > Recordings > Recording Rules.
- Create a rule with condition:
Conversation Attributeequalsforce_recordtrue. - Ensure the rule applies to the queue or routing strategy of the conversation.
- Verify the call is external or involves a monitored queue.