Programmatically Start and Stop Call Recordings via the Genesys Cloud Recording API
What You Will Build
- A script that initiates a recording for an active conversation and stops it on command.
- This tutorial uses the Genesys Cloud CX Recording API (
/api/v2/recordings). - The implementation covers Python using the
genesyscloudSDK and rawhttpxfor HTTP-level control.
Prerequisites
Before writing code, ensure your environment meets these requirements.
- OAuth Client: You need a Genesys Cloud OAuth client with the
client_credentialsgrant type. - Required Scopes:
recording:read(to list or check status)recording:write(to start or stop recordings)conversation:read(optional, if you need to correlate conversation metadata)
- SDK Version:
genesyscloud>= 2.0.0 (Python). - Runtime: Python 3.8+.
- Dependencies:
pip install genesyscloud httpx
Authentication Setup
Genesys Cloud uses OAuth 2.0. For server-to-server integrations, the client_credentials flow is standard. You must cache the access token and handle expiration. The following example uses httpx to manage the token lifecycle, ensuring you do not hit rate limits by requesting a new token on every API call.
import httpx
import time
import os
class GenesysAuth:
def __init__(self, client_id: str, client_secret: str, environment: str = "mypurecloud.com"):
self.client_id = client_id
self.client_secret = client_secret
self.base_url = f"https://api.{environment}"
self.token_url = f"{self.base_url}/oauth/token"
self.access_token: str | None = None
self.token_expiry: float = 0
self.http_client = httpx.Client()
def get_access_token(self) -> str:
"""
Retrieves an OAuth access token. Caches it until 60 seconds before expiry
to prevent race conditions during API calls.
"""
if self.access_token and time.time() < self.token_expiry - 60:
return self.access_token
try:
response = self.http_client.post(
self.token_url,
data={"grant_type": "client_credentials"},
auth=(self.client_id, self.client_secret),
headers={"Content-Type": "application/x-www-form-urlencoded"}
)
response.raise_for_status()
data = response.json()
self.access_token = data["access_token"]
# Genesys tokens expire in 3600s by default. Subtract buffer.
self.token_expiry = time.time() + data["expires_in"] - 60
return self.access_token
except httpx.HTTPStatusError as e:
raise Exception(f"OAuth authentication failed: {e.response.status_code} - {e.response.text}") from e
# Usage
# auth = GenesysAuth(os.getenv("GENESYS_CLIENT_ID"), os.getenv("GENESYS_CLIENT_SECRET"))
# token = auth.get_access_token()
Implementation
Step 1: Identify the Active Conversation
You cannot record a conversation unless you know its ID. In a real-world scenario, you would hook into the Conversations API or use Webhooks to detect when a call connects. For this tutorial, we assume you have an active conversation ID. If you do not, you can list active conversations to find one.
Endpoint: GET /api/v2/conversations/voice/active
Scope: conversation:read
def get_active_voice_conversation(auth: GenesysAuth) -> str | None:
"""
Retrieves the first active voice conversation ID.
Returns None if no active conversations are found.
"""
token = auth.get_access_token()
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/json"
}
url = f"{auth.base_url}/api/v2/conversations/voice/active"
try:
response = auth.http_client.get(url, headers=headers)
response.raise_for_status()
data = response.json()
entities = data.get("entities", [])
if not entities:
print("No active voice conversations found.")
return None
# Return the ID of the first active conversation
conv_id = entities[0]["id"]
print(f"Found active conversation: {conv_id}")
return conv_id
except httpx.HTTPStatusError as e:
if e.response.status_code == 401:
print("Token expired or invalid. Refreshing token and retrying...")
# In a robust implementation, you would call auth.get_access_token() again here
# and retry the request. For brevity, we raise.
raise
raise Exception(f"Failed to fetch conversations: {e}") from e
Step 2: Start the Recording
Once you have the conversation ID, you initiate the recording. The Genesys Cloud Recording API requires you to specify the conversationId and the mediaType (usually voice for phone calls, screen for desktop sharing).
Endpoint: POST /api/v2/recordings
Scope: recording:write
Request Body: JSON payload containing conversationId and mediaType.
def start_recording(auth: GenesysAuth, conversation_id: str, media_type: str = "voice") -> str | None:
"""
Starts a recording for the specified conversation.
Returns the recording ID if successful, None otherwise.
"""
token = auth.get_access_token()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
url = f"{auth.base_url}/api/v2/recordings"
payload = {
"conversationId": conversation_id,
"mediaType": media_type
}
try:
response = auth.http_client.post(url, json=payload, headers=headers)
# 201 Created is the standard success response for resource creation
if response.status_code == 201:
data = response.json()
recording_id = data.get("id")
print(f"Recording started successfully. ID: {recording_id}")
return recording_id
else:
print(f"Failed to start recording. Status: {response.status_code}")
print(f"Response: {response.text}")
return None
except httpx.HTTPStatusError as e:
# Handle specific errors
if e.response.status_code == 409:
print("Recording already exists for this conversation or media type.")
elif e.response.status_code == 404:
print("Conversation not found.")
else:
print(f"HTTP Error: {e.response.status_code} - {e.response.text}")
return None
except Exception as e:
print(f"Unexpected error starting recording: {e}")
return None
Step 3: Stop the Recording
To stop a recording, you must know the recordingId (returned when you started it) and the conversationId. You also need to provide the stopReason. Common reasons include user-requested, timeout, or error.
Endpoint: POST /api/v2/recordings/{recordingId}/stop
Scope: recording:write
def stop_recording(auth: GenesysAuth, conversation_id: str, recording_id: str, stop_reason: str = "user-requested") -> bool:
"""
Stops an existing recording.
Returns True if successful, False otherwise.
"""
token = auth.get_access_token()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
# The endpoint requires both the recording ID in the path and the conversation ID in the body
url = f"{auth.base_url}/api/v2/recordings/{recording_id}/stop"
payload = {
"conversationId": conversation_id,
"stopReason": stop_reason
}
try:
response = auth.http_client.post(url, json=payload, headers=headers)
# 204 No Content is expected for successful stop operations
if response.status_code == 204:
print(f"Recording {recording_id} stopped successfully.")
return True
else:
print(f"Failed to stop recording. Status: {response.status_code}")
print(f"Response: {response.text}")
return False
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
print("Recording or Conversation not found. It may have already ended.")
elif e.response.status_code == 400:
print("Bad Request. Check if the recording is already stopped.")
else:
print(f"HTTP Error: {e.response.status_code} - {e.response.text}")
return False
except Exception as e:
print(f"Unexpected error stopping recording: {e}")
return False
Complete Working Example
This script combines the authentication, discovery, start, and stop logic. It simulates a workflow where it finds a call, records it for 10 seconds, and then stops it.
Note: In a production environment, you would not hardcode the wait time. You would trigger the stop via an event (e.g., button click in a GUI, webhook from agent, or conversation end).
import os
import time
import sys
from typing import Optional
# Import the classes defined in the previous sections
# In a real file, ensure GenesysAuth, get_active_voice_conversation,
# start_recording, and stop_recording are defined or imported.
def main():
# 1. Setup Credentials
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
if not client_id or not client_secret:
print("Error: GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
sys.exit(1)
# Initialize Authentication
auth = GenesysAuth(client_id, client_secret)
try:
# 2. Find an active conversation
print("Looking for an active voice conversation...")
conversation_id = get_active_voice_conversation(auth)
if not conversation_id:
print("No active conversations found. Please place a test call and run again.")
return
# 3. Start Recording
print("Starting recording...")
recording_id = start_recording(auth, conversation_id)
if not recording_id:
print("Could not start recording. Aborting.")
return
# 4. Wait for a duration (Simulating the call duration)
print(f"Recording is active for 10 seconds...")
time.sleep(10)
# 5. Stop Recording
print("Stopping recording...")
success = stop_recording(auth, conversation_id, recording_id)
if success:
print("Workflow completed successfully.")
else:
print("Failed to stop recording cleanly.")
except Exception as e:
print(f"Critical failure in workflow: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 409 Conflict
- What causes it: You attempted to start a recording for a conversation that already has an active recording of the same
mediaType. Genesys Cloud does not allow multiple concurrent recordings of the same type for a single conversation. - How to fix it: Check the recording status before starting. If a recording exists, either stop it first or skip starting a new one.
- Code Fix:
# Before starting, check existing recordings def check_existing_recording(auth: GenesysAuth, conversation_id: str) -> bool: # GET /api/v2/recordings/conversations/{conversationId}/recordings # If the list is not empty, a recording exists. pass
Error: 403 Forbidden
- What causes it: The OAuth token does not have the
recording:writescope. - How to fix it: Go to the Genesys Cloud Admin Console > Platform > Applications > OAuth. Edit your client and ensure
recording:writeis checked. Re-generate the token.
Error: 404 Not Found
- What causes it: The
conversationIdorrecordingIdis invalid. This often happens if the call ended while your script was still running, or if you are using a stale ID. - How to fix it: Verify the conversation is still active. If you are stopping a recording, ensure the recording has not already been automatically stopped due to conversation termination.
Error: 429 Too Many Requests
- What causes it: You are exceeding the API rate limits. The Recording API has specific limits per tenant.
- How to fix it: Implement exponential backoff in your HTTP client.
- Code Fix:
import time def make_request_with_retry(auth: GenesysAuth, method: str, url: str, **kwargs) -> httpx.Response: max_retries = 3 for attempt in range(max_retries): try: response = getattr(auth.http_client, method)(url, **kwargs) if response.status_code == 429: wait_time = 2 ** attempt print(f"Rate limited. Waiting {wait_time} seconds...") time.sleep(wait_time) continue return response except httpx.HTTPStatusError as e: if e.response.status_code == 429: wait_time = 2 ** attempt print(f"Rate limited. Waiting {wait_time} seconds...") time.sleep(wait_time) continue raise raise Exception("Max retries exceeded due to rate limiting.")