Programmatically Start and Stop Call Recordings via the Genesys Cloud API

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/conversations and api/v2/conversations endpoints to manage recording states.
  • The examples are provided in Python using the requests library and the official genesys-cloud-purecloud-platform-client SDK.

Prerequisites

  • OAuth Client Type: A Genesys Cloud OAuth Client with the following scopes:
    • conversations:read
    • conversations:write
    • recording:read
    • recording:write
    • analytics: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-client
    • pip 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:

  1. Updating Conversation Attributes: Some recording rules evaluate dynamic attributes. You can push data into the conversation to trigger a rule-based recording start.
  2. Using the Recording API (Limited): The recording API 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.
  3. 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.

  1. In the Genesys Cloud Admin Console, create a Recording Rule that triggers when a specific attribute (e.g., force_record) is set to true.
  2. 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:write and recording:write scopes. 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}/recordings endpoint 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 state field in the recording object. Only call /stop if the state is active or paused. Only call /pause if the state is active.

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:
    1. Go to Admin > Recordings > Recording Rules.
    2. Create a rule with condition: Conversation Attribute equals force_record true.
    3. Ensure the rule applies to the queue or routing strategy of the conversation.
    4. Verify the call is external or involves a monitored queue.

Official References