How to Mute/Unmute an Agent Microphone via the Genesys Cloud API
What You Will Build
- You will write code to programmatically mute and unmute an agent’s microphone during an active voice conversation.
- This tutorial utilizes the Genesys Cloud v2 Conversations API (
/api/v2/conversations/voice/participants/{participantId}). - The primary implementation language is Python, with supplementary examples in JavaScript/TypeScript.
Prerequisites
- OAuth Client Type: You require a Client Credentials grant flow client. Ensure the client has the necessary permissions to modify conversation participants.
- Required Scopes: The OAuth token must include the
conversation:participant:writescope. Without this, the API will return a 403 Forbidden error. - SDK Version: Genesys Cloud Python SDK (
genesyscloud) version 1.0.0 or later. For JavaScript, use the@genesyscloud/platform-client-sdkpackage. - Runtime Requirements: Python 3.8+ or Node.js 16+.
- External Dependencies:
- Python:
pip install genesyscloud requests - JavaScript:
npm install @genesyscloud/platform-client-sdk axios
- Python:
Authentication Setup
Before interacting with the Conversations API, you must obtain a valid access token. The Genesys Cloud API uses OAuth 2.0. For server-side integrations that control agent states or conversation parameters, the Client Credentials flow is standard.
Python Authentication Helper
Create a helper function to fetch the token. In a production environment, cache this token and refresh it before expiration. Do not fetch a new token for every API call.
import requests
import time
from typing import Optional
class GenesysAuth:
def __init__(self, client_id: str, client_secret: str, region: str = "mypurecloud.com"):
self.client_id = client_id
self.client_secret = client_secret
self.region = region
self.token_url = f"https://api.{region}/oauth/token"
self.access_token: Optional[str] = None
self.token_expiry: float = 0
def get_access_token(self) -> str:
# Return cached token if valid
if self.access_token and time.time() < self.token_expiry:
return self.access_token
# Fetch new token
response = requests.post(
self.token_url,
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
)
if response.status_code != 200:
raise Exception(f"Failed to obtain token: {response.status_code} - {response.text}")
data = response.json()
self.access_token = data["access_token"]
# Cache for 90% of the lifetime to avoid edge-case expiration
self.token_expiry = time.time() + (data["expires_in"] * 0.9)
return self.access_token
Implementation
Step 1: Identify the Active Conversation and Participant
You cannot mute a microphone without knowing the specific conversationId and participantId. The agent must be in an active voice conversation.
- Find the Conversation: Query the user’s current interactions.
- Find the Participant: Locate the participant object corresponding to the agent.
Finding the Active Voice Conversation (Python)
Use the GET /api/v2/conversations/voice endpoint or the user-specific interaction endpoint. The most reliable method for a specific agent is querying their current interactions.
import requests
from typing import Dict, Any
class GenesysConversationManager:
def __init__(self, auth: GenesysAuth):
self.auth = auth
self.base_url = f"https://api.{auth.region}"
def get_active_voice_conversation(self, user_id: str) -> Dict[str, Any]:
"""
Retrieves the first active voice conversation for a given user.
"""
token = self.auth.get_access_token()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# Fetch all current conversations for the user
# Note: In high-volume scenarios, paginate if necessary.
# For a single agent check, the default limit is usually sufficient.
url = f"{self.base_url}/api/v2/conversations/voice"
params = {
"pageSize": 100,
"filter": f"participants.userId eq '{user_id}'"
}
response = requests.get(url, headers=headers, params=params)
if response.status_code != 200:
raise Exception(f"Failed to fetch conversations: {response.status_code} - {response.text}")
conversations = response.json().get("entities", [])
# Filter for active conversations (state: 'connected' or 'ringing')
active_convo = None
for convo in conversations:
if convo.get("state") in ["connected", "ringing", "holding"]:
active_convo = convo
break
if not active_convo:
raise Exception(f"No active voice conversation found for user {user_id}")
return active_convo
Step 2: Locate the Participant ID
Once you have the conversation object, you must iterate through its participants array to find the one matching the agent’s userId.
def get_agent_participant_id(self, conversation: Dict[str, Any], user_id: str) -> str:
"""
Finds the participantId for the specific user within the conversation.
"""
participants = conversation.get("participants", [])
for participant in participants:
if participant.get("userId") == user_id:
return participant["id"]
raise Exception(f"User {user_id} is not a participant in conversation {conversation['id']}")
Step 3: Execute the Mute/Unmute Command
The core action uses the PATCH /api/v2/conversations/voice/participants/{participantId} endpoint.
Critical Parameter: The muted field.
true: Mutes the agent’s microphone (no audio sent from agent to others).false: Unmutes the agent’s microphone.
Required Headers:
Content-Type: application/jsonAuthorization: Bearer <token>
Request Body:
{
"muted": true
}
Python Implementation: Mute/Unmute
def toggle_mute(self, conversation_id: str, participant_id: str, muted: bool) -> Dict[str, Any]:
"""
Mutes or unmutes a participant in a voice conversation.
Args:
conversation_id: The ID of the voice conversation.
participant_id: The ID of the participant to mute.
muted: True to mute, False to unmute.
"""
token = self.auth.get_access_token()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# Construct the URL for the specific participant in the specific conversation
# Note: The API path is /conversations/voice/participants/{participantId}
# The conversation context is implicitly handled by the participant belonging to that conversation.
url = f"{self.base_url}/api/v2/conversations/voice/participants/{participant_id}"
payload = {
"muted": muted
}
# Use PATCH method as per API specification
response = requests.patch(url, headers=headers, json=payload)
if response.status_code == 200:
return response.json()
elif response.status_code == 404:
raise Exception(f"Participant {participant_id} not found in conversation.")
elif response.status_code == 403:
raise Exception("Insufficient permissions. Ensure 'conversation:participant:write' scope is present.")
else:
raise Exception(f"Failed to toggle mute: {response.status_code} - {response.text}")
JavaScript/TypeScript Implementation
For frontend or Node.js environments, use the official SDK or Axios. The logic remains identical.
import { PlatformClient } from '@genesyscloud/platform-client-sdk';
interface GenesysConfig {
clientId: string;
clientSecret: string;
region: string;
}
export class GenesysCallController {
private platformClient: PlatformClient;
constructor(config: GenesysConfig) {
this.platformClient = new PlatformClient(config.region);
// Initialize OAuth (assuming client credentials)
this.platformClient.login(config.clientId, config.clientSecret);
}
async toggleMute(conversationId: string, participantId: string, muted: boolean): Promise<void> {
try {
// The SDK method for updating a participant
// Note: In the JS SDK, the method is typically part of the ConversationsVoiceApi
const conversationsVoiceApi = this.platformClient.ConversationsVoiceApi;
const body = {
muted: muted
};
// Execute the update
await conversationsVoiceApi.postConversationsVoiceParticipantsUpdate({
conversationId: conversationId,
participantId: participantId,
body: body
});
console.log(`Successfully set muted=${muted} for participant ${participantId}`);
} catch (error) {
if (error instanceof Error) {
console.error("Error toggling mute:", error.message);
}
throw error;
}
}
async findActiveConversationForUser(userId: string): Promise<string | null> {
const conversationsVoiceApi = this.platformClient.ConversationsVoiceApi;
// Fetch conversations
const response = await conversationsVoiceApi.getConversationsVoice({
pageSize: 100,
filter: `participants.userId eq '${userId}'`
});
const entities = response.entities || [];
const activeConvo = entities.find(c => ['connected', 'ringing', 'holding'].includes(c.state));
return activeConvo ? activeConvo.id : null;
}
}
Complete Working Example
Below is a complete, runnable Python script. It assumes you have stored your credentials in environment variables.
import os
import sys
import time
import requests
from typing import Optional, Dict, Any
# --- Configuration ---
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
REGION = os.getenv("GENESYS_REGION", "mypurecloud.com")
AGENT_USER_ID = os.getenv("AGENT_USER_ID") # The ID of the agent to control
if not all([CLIENT_ID, CLIENT_SECRET, AGENT_USER_ID]):
print("Error: Missing environment variables. Set GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET, and AGENT_USER_ID.")
sys.exit(1)
# --- Authentication Class ---
class GenesysAuth:
def __init__(self, client_id: str, client_secret: str, region: str):
self.client_id = client_id
self.client_secret = client_secret
self.region = region
self.token_url = f"https://api.{region}/oauth/token"
self.access_token: Optional[str] = None
self.token_expiry: float = 0
def get_access_token(self) -> str:
if self.access_token and time.time() < self.token_expiry:
return self.access_token
response = requests.post(
self.token_url,
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
)
if response.status_code != 200:
raise Exception(f"Auth failed: {response.status_code} - {response.text}")
data = response.json()
self.access_token = data["access_token"]
self.token_expiry = time.time() + (data["expires_in"] * 0.9)
return self.access_token
# --- Conversation Manager Class ---
class GenesysConversationManager:
def __init__(self, auth: GenesysAuth):
self.auth = auth
self.base_url = f"https://api.{auth.region}"
def get_headers(self) -> Dict[str, str]:
return {
"Authorization": f"Bearer {self.auth.get_access_token()}",
"Content-Type": "application/json"
}
def get_active_voice_conversation(self, user_id: str) -> Dict[str, Any]:
url = f"{self.base_url}/api/v2/conversations/voice"
params = {
"pageSize": 100,
"filter": f"participants.userId eq '{user_id}'"
}
response = requests.get(url, headers=self.get_headers(), params=params)
if response.status_code != 200:
raise Exception(f"Fetch conversations failed: {response.status_code} - {response.text}")
conversations = response.json().get("entities", [])
for convo in conversations:
if convo.get("state") in ["connected", "ringing", "holding"]:
return convo
raise Exception(f"No active voice conversation found for user {user_id}")
def get_agent_participant_id(self, conversation: Dict[str, Any], user_id: str) -> str:
participants = conversation.get("participants", [])
for participant in participants:
if participant.get("userId") == user_id:
return participant["id"]
raise Exception(f"User {user_id} not found in conversation {conversation['id']}")
def toggle_mute(self, participant_id: str, muted: bool) -> Dict[str, Any]:
url = f"{self.base_url}/api/v2/conversations/voice/participants/{participant_id}"
payload = {"muted": muted}
response = requests.patch(url, headers=self.get_headers(), json=payload)
if response.status_code == 200:
return response.json()
elif response.status_code == 403:
raise Exception("403 Forbidden: Check OAuth scopes. Need 'conversation:participant:write'.")
else:
raise Exception(f"Toggle mute failed: {response.status_code} - {response.text}")
# --- Main Execution Logic ---
def main():
auth = GenesysAuth(CLIENT_ID, CLIENT_SECRET, REGION)
manager = GenesysConversationManager(auth)
print(f"Checking active conversations for Agent ID: {AGENT_USER_ID}")
try:
# 1. Get Active Conversation
conversation = manager.get_active_voice_conversation(AGENT_USER_ID)
convo_id = conversation["id"]
print(f"Found active conversation: {convo_id}")
# 2. Get Participant ID
participant_id = manager.get_agent_participant_id(conversation, AGENT_USER_ID)
print(f"Participant ID: {participant_id}")
# 3. Mute the Agent
print("Muting agent microphone...")
mute_result = manager.toggle_mute(participant_id, muted=True)
print(f"Mute successful. State: {mute_result.get('muted')}")
# Wait 5 seconds to demonstrate effect
print("Waiting 5 seconds...")
time.sleep(5)
# 4. Unmute the Agent
print("Unmuting agent microphone...")
unmute_result = manager.toggle_mute(participant_id, muted=False)
print(f"Unmute successful. State: {unmute_result.get('muted')}")
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden
Cause: The OAuth token does not have the required scope.
Fix: Verify that your OAuth Client Credentials configuration in the Genesys Cloud Admin Portal includes the conversation:participant:write scope.
Debugging:
- Go to Admin > Security > OAuth Clients.
- Select your client.
- Check the Scopes tab.
- Ensure
conversation:participant:writeis checked. - Regenerate the token.
Error: 404 Not Found
Cause: The participantId or conversationId is invalid, or the conversation has ended.
Fix:
- Ensure the conversation is still active. If the agent hangs up, the participant object may be removed or archived.
- Verify that the
userIdpassed toget_active_voice_conversationmatches theAGENT_USER_IDexactly. - Check if the agent is actually in a voice conversation. If they are idle, no conversation entity will be returned.
Error: 409 Conflict
Cause: Attempting to update a participant in a conversation that has already terminated or is in a state that does not allow modification.
Fix:
- Check the
stateof the conversation. Onlyconnected,ringing, andholdingstates typically allow participant updates. - If the conversation is
ended,abandoned, ortransferred, the API may reject the PATCH request.
Error: 429 Too Many Requests
Cause: Rate limiting. Genesys Cloud APIs enforce rate limits per client ID.
Fix: Implement exponential backoff. Do not retry immediately.
Code Snippet for Retry:
import time
import random
def request_with_retry(func, *args, max_retries=3, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
if "429" in str(e) and attempt < max_retries - 1:
wait_time = (2 ** attempt) + random.uniform(0, 1)
print(f"Rate limited. Retrying in {wait_time:.2f} seconds...")
time.sleep(wait_time)
else:
raise e