Updating Participant Attributes Mid-Session via the Genesys Cloud Conversations API
What You Will Build
This tutorial demonstrates how to programmatically update participant-specific metadata (such as custom attributes or state) for an active conversation using the Genesys Cloud Conversations API. You will build a Python script that locates a specific participant within a live session and applies a JSON payload to modify their attributes without terminating the call or chat. This uses the Genesys Cloud conversations API endpoint with the PureCloudPlatformClientV2 SDK.
Prerequisites
- OAuth Client Type: A Genesys Cloud API Client configured with Client Credentials flow.
- Required Scopes:
conversations:participant:writeis mandatory for modifying participant data.conversations:viewis required to locate the conversation and participant IDs if they are not already known. - SDK Version:
genesys-cloud-purecloud-platform-clientv175.0.0 or higher. - Runtime: Python 3.8+.
- Dependencies:
pip install genesys-cloud-purecloud-platform-client
Authentication Setup
The Genesys Cloud APIs require a valid Bearer token for every request. The SDK handles the underlying HTTP transport, but you must initialize the client with your environment, client ID, and client secret. The SDK includes a built-in token cache that automatically handles token expiration and refresh, so you do not need to manually manage token lifecycle in your application logic.
import os
from genesyscloudplatformclientv2 import (
ApiClient,
Configuration,
ConversationApi,
ParticipantApi
)
def get_api_client() -> ApiClient:
"""
Initializes and returns a configured Genesys Cloud API Client.
Uses environment variables for sensitive credentials.
"""
# Load credentials from environment variables
environment = os.getenv("GENESYS_ENV", "mypurecloud.com")
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
if not all([environment, client_id, client_secret]):
raise ValueError("Missing required environment variables: GENESYS_ENV, GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET")
# Create configuration object
config = Configuration(
host=f"https://{environment}",
client_id=client_id,
client_secret=client_secret
)
# Initialize API Client with OAuth2 automatic handling
api_client = ApiClient(configuration=config)
return api_client
def get_participant_api() -> ParticipantApi:
"""
Returns an instantiated ParticipantApi object ready for calls.
"""
api_client = get_api_client()
return ParticipantApi(api_client)
Implementation
Updating participant attributes requires a specific sequence of operations. You cannot update a participant attribute using a generic PUT on the conversation object. You must target the specific participantId within the specific conversationId.
Step 1: Identify the Conversation and Participant
Before updating attributes, you must possess the conversationId and the participantId. If your integration receives these IDs from a webhook (e.g., conversation:created or conversation:updated), you can skip this step. If you are building a utility script, you must query the active conversations.
The following example assumes you have a known conversationId but need to find the specific participant. We will list all participants in the conversation to find the one matching a specific identifier (e.g., a phone number or email address stored in the participant’s external ID or custom attributes).
Endpoint: GET /api/v2/conversations/{conversationId}/participants
Scope: conversations:view
def find_participant_by_external_id(
participant_api: ParticipantApi,
conversation_id: str,
target_external_id: str
) -> str | None:
"""
Searches for a participant in a conversation matching a specific external ID.
Returns the participantId if found, None otherwise.
"""
try:
# List all participants in the conversation
# Note: For very large conferences, pagination might be required.
# This example assumes a standard 1:1 or small group interaction.
response = participant_api.get_conversations_participants(
conversation_id=conversation_id
)
if not response.entities:
print("No participants found in conversation.")
return None
for participant in response.entities:
# Check if the participant has an externalId matching our target
# Note: externalId is a standard field often set during wrap-up or initial connection
if participant.external_id and participant.external_id == target_external_id:
print(f"Found participant {participant.id} with externalId {target_external_id}")
return participant.id
# Alternative: Check custom attributes if they exist
if participant.attributes:
if participant.attributes.get("target_identifier") == target_external_id:
print(f"Found participant {participant.id} via custom attribute")
return participant.id
print("Participant with matching identifier not found.")
return None
except Exception as e:
print(f"Error fetching participants: {e}")
return None
Step 2: Construct the Attribute Payload
Genesys Cloud participant attributes are stored as a JSON object. The API expects a ParticipantPatch or ParticipantUpdate body. When updating attributes, you typically send the entire attributes object you want the participant to possess, or specific fields depending on the endpoint behavior.
For the patch endpoint, you send a partial update. This is safer for concurrent updates.
Endpoint: PATCH /api/v2/conversations/{conversationId}/participants/{participantId}
Scope: conversations:participant:write
The payload structure for attributes is:
{
"attributes": {
"custom_key_1": "new_value",
"custom_key_2": 123
}
}
It is critical to understand that the attributes field is a map. If you send {"attributes": {"key_a": "val_a"}}, only key_a is updated. Existing keys (key_b, key_c) remain untouched. This is distinct from replacing the entire participant object.
Step 3: Execute the Update with Error Handling
The core logic involves calling the patch_conversations_participant method. This method accepts the conversation ID, participant ID, and the body payload.
You must handle 429 Too Many Requests errors. Genesys Cloud enforces strict rate limits. If you are processing high volumes of conversations (e.g., during a campaign wrap-up), you will hit 429s. The SDK does not automatically retry 429s for all methods, so explicit retry logic is recommended.
import time
import json
def update_participant_attributes(
participant_api: ParticipantApi,
conversation_id: str,
participant_id: str,
attributes_to_add: dict
) -> bool:
"""
Updates specific attributes for a participant in a conversation.
Implements basic retry logic for 429 errors.
"""
# Construct the patch body
# We only send the attributes we wish to update
body = {
"attributes": attributes_to_add
}
max_retries = 3
retry_count = 0
while retry_count < max_retries:
try:
# Perform the PATCH request
# The SDK maps the dictionary to the ParticipantPatch model automatically
response = participant_api.patch_conversations_participant(
conversation_id=conversation_id,
participant_id=participant_id,
body=body
)
print(f"Successfully updated participant {participant_id}")
print(f"Response attributes: {json.dumps(response.attributes, indent=2)}")
return True
except Exception as e:
# Check for 429 Too Many Requests
# The SDK raises a specific exception for HTTP errors
if hasattr(e, 'status') and e.status == 429:
retry_count += 1
wait_time = 2 ** retry_count # Exponential backoff: 2s, 4s, 8s
print(f"Rate limited (429). Retrying in {wait_time} seconds... (Attempt {retry_count}/{max_retries})")
time.sleep(wait_time)
continue
elif hasattr(e, 'status') and e.status == 404:
print(f"Error 404: Conversation or Participant not found. IDs: {conversation_id}, {participant_id}")
return False
elif hasattr(e, 'status') and e.status == 403:
print(f"Error 403: Forbidden. Check if your client has 'conversations:participant:write' scope.")
return False
else:
# Handle other unexpected errors
print(f"Unexpected error: {e}")
return False
print("Max retries exceeded for 429 errors.")
return False
Complete Working Example
The following script combines all steps. It authenticates, finds a participant in a known conversation by their external ID, and updates their attributes.
Requirements:
- Set environment variables:
GENESYS_ENV,GENESYS_CLIENT_ID,GENESYS_CLIENT_SECRET. - Have an active conversation ID.
- Know the external ID or a unique identifier of a participant in that conversation.
import os
import sys
import json
from genesyscloudplatformclientv2 import (
ApiClient,
Configuration,
ParticipantApi
)
def initialize_client() -> ParticipantApi:
env = os.getenv("GENESYS_ENV", "mypurecloud.com")
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
if not all([env, client_id, client_secret]):
raise EnvironmentError("Missing GENESYS_ENV, GENESYS_CLIENT_ID, or GENESYS_CLIENT_SECRET")
config = Configuration(
host=f"https://{env}",
client_id=client_id,
client_secret=client_secret
)
api_client = ApiClient(configuration=config)
return ParticipantApi(api_client)
def main():
# Configuration
CONVERSATION_ID = "12345678-1234-1234-1234-123456789012" # Replace with real ID
TARGET_EXTERNAL_ID = "customer_12345" # Replace with real external ID
# Attributes to update
NEW_ATTRIBUTES = {
"vip_status": True,
"support_tier": "platinum",
"last_interaction_type": "phone"
}
try:
# 1. Initialize API
print("Initializing Genesys Cloud Client...")
participant_api = initialize_client()
# 2. Find Participant
print(f"Searching for participant with externalId '{TARGET_EXTERNAL_ID}' in conversation {CONVERSATION_ID}...")
participant_id = find_participant_by_external_id(
participant_api,
CONVERSATION_ID,
TARGET_EXTERNAL_ID
)
if not participant_id:
print("Failed to locate participant. Exiting.")
sys.exit(1)
# 3. Update Attributes
print(f"Updating attributes for participant {participant_id}...")
success = update_participant_attributes(
participant_api,
CONVERSATION_ID,
participant_id,
NEW_ATTRIBUTES
)
if success:
print("Operation completed successfully.")
else:
print("Operation failed.")
sys.exit(1)
except Exception as e:
print(f"Fatal error: {e}")
sys.exit(1)
def find_participant_by_external_id(participant_api, conversation_id, target_external_id):
try:
response = participant_api.get_conversations_participants(conversation_id=conversation_id)
for p in response.entities:
if p.external_id == target_external_id:
return p.id
return None
except Exception as e:
print(f"Error listing participants: {e}")
return None
def update_participant_attributes(participant_api, conversation_id, participant_id, attributes_to_add):
body = {"attributes": attributes_to_add}
max_retries = 3
for attempt in range(1, max_retries + 1):
try:
participant_api.patch_conversations_participant(
conversation_id=conversation_id,
participant_id=participant_id,
body=body
)
return True
except Exception as e:
if hasattr(e, 'status') and e.status == 429:
import time
time.sleep(2 ** attempt)
continue
else:
print(f"Error updating participant: {e}")
return False
return False
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden
Cause: The OAuth client used to generate the token does not have the conversations:participant:write scope.
Fix:
- Go to the Genesys Cloud Admin console.
- Navigate to Setup > Integration > API Access.
- Select your client.
- Edit the permissions.
- Ensure
conversations:participant:writeis checked. - Regenerate the token or restart your application to pick up the new scope (scopes are bound to the token at issuance time).
Error: 404 Not Found
Cause: The conversationId or participantId is invalid, expired, or does not belong to the organization.
Fix:
- Verify the
conversationIdis active. Use theGET /api/v2/conversations/{conversationId}endpoint to check status. - Verify the
participantIdexists within that conversation. Participant IDs are unique only within the context of a specific conversation. You cannot use a participant ID from Conversation A in Conversation B.
Error: 429 Too Many Requests
Cause: You have exceeded the rate limit for the conversations API domain.
Fix:
- Implement exponential backoff retry logic as shown in the implementation step.
- Check the
Retry-Afterheader in the response payload if available. - Consider batching updates if you are modifying multiple participants, though note that participant updates are atomic per participant and cannot be batched in a single API call.
Error: Attributes Not Persisting
Cause: You are overwriting the attributes object incorrectly or the participant is in a state that prevents updates (e.g., already wrapped up and archived).
Fix:
- Ensure you are using the
PATCHmethod, notPUT.PUTreplaces the entire participant object, which can cause validation errors if mandatory fields (likestateormediaType) are missing from your body.PATCHmerges the changes. - Check if the conversation is still active. Once a conversation is archived, participant attributes are generally read-only for operational purposes.