Reading and writing participant attributes from an external system during a live voice call
What You Will Build
- This tutorial demonstrates how to read and update user-defined attributes on a specific conversation participant in real time using the Genesys Cloud CX API.
- The implementation uses the Genesys Cloud CX REST API via the Python
genesys-cloudSDK. - The code is written in Python 3.8+ and handles the lifecycle of a live voice conversation.
Prerequisites
- OAuth Client Type: Confidential Client (Client Credentials Grant) or JWT Grant.
- Required Scopes:
conversation:participant:readto retrieve existing attributes.conversation:participant:writeto update or add attributes.conversation:viewto query for active conversations.
- SDK Version:
genesys-cloudPython SDK v1.0.0 or later. - Runtime Requirements: Python 3.8+.
- External Dependencies:
pip install genesys-cloud
Authentication Setup
Genesys Cloud uses OAuth 2.0 for API authentication. For server-side integrations that read/write data during live calls, the JWT Grant flow is typically preferred because it allows your application to generate short-lived access tokens on demand without managing long-lived client secrets in the same way as the Client Credentials flow. However, the Client Credentials flow is simpler to set up for initial testing.
The following example uses the genesys-cloud Python SDK’s built-in configuration helper, which abstracts the token acquisition. You must provide your API Key ID and API Secret.
import os
from genesyscloud import Configuration
def get_genesys_config():
"""
Initializes the Genesys Cloud configuration with OAuth2 credentials.
Uses environment variables for security.
"""
# Load credentials from environment variables
api_key_id = os.getenv("GENESYS_API_KEY_ID")
api_secret = os.getenv("GENESYS_API_SECRET")
region = os.getenv("GENESYS_REGION", "us-east-1")
if not api_key_id or not api_secret:
raise EnvironmentError("GENESYS_API_KEY_ID and GENESYS_API_SECRET must be set.")
# Initialize configuration
config = Configuration()
config.region = region
config.api_key['Authorization'] = api_key_id
config.api_key_prefix['Authorization'] = 'Bearer'
# The SDK handles token generation internally when using the API client
# We pass the secret separately to the client initialization in some patterns,
# but the standard Configuration approach often relies on a separate auth module
# or passing the secret to the ApiClient constructor if using raw HTTP.
# For the SDK wrapper, we typically use the PureCloudPlatformClientV2
# which handles the JWT or Client Credentials flow internally if configured correctly.
# Here we assume a standard JWT setup or Client Credentials setup.
# Note: The Python SDK 'genesys-cloud' v1+ uses a simpler init pattern.
return config
For production, use the genesys-cloud SDK’s PureCloudPlatformClientV2 which manages token caching and refresh.
from genesyscloud.purecloud import PureCloudPlatformClientV2
def get_platform_client():
"""
Returns an authenticated PureCloudPlatformClientV2 instance.
"""
api_key_id = os.getenv("GENESYS_API_KEY_ID")
api_secret = os.getenv("GENESYS_API_SECRET")
# Initialize client with credentials
client = PureCloudPlatformClientV2(
api_key_id=api_key_id,
api_secret=api_secret
)
return client
Implementation
Step 1: Locate the Active Conversation and Participant
Before modifying attributes, you must identify the specific conversation and the participant within it. Participant attributes are scoped to a specific participant in a specific conversation. A user may have different attributes in different conversations.
We will query for an active voice conversation associated with a specific user (e.g., the agent or the customer).
Endpoint: GET /api/v2/conversations
Scope: conversation:view
from genesyscloud.conversations import ConversationsApi
from genesyscloud.conversations.models import ConversationQueryRequest
def find_active_voice_conversation(platform_client: PureCloudPlatformClientV2, user_id: str) -> str:
"""
Finds the most recent active voice conversation for a given user ID.
Args:
platform_client: The authenticated Genesys Cloud client.
user_id: The ID of the user (agent or customer) to search for.
Returns:
The conversation ID string, or None if not found.
"""
api_instance = ConversationsApi(platform_client)
# Define query parameters
# We filter by type 'voice' and status 'active'
# We also filter by the specific user ID
body = ConversationQueryRequest(
types=["voice"],
statuses=["active"],
user_ids=[user_id],
sort_order="desc" # Most recent first
)
try:
# Query conversations
response = api_instance.post_conversations_query(body=body)
if response.entities and len(response.entities) > 0:
return response.entities[0].id
else:
print(f"No active voice conversations found for user {user_id}")
return None
except Exception as e:
print(f"Error querying conversations: {e}")
raise
Step 2: Read Existing Participant Attributes
Once you have the conversation_id, you need to retrieve the specific participant object to read their current attributes. Attributes are stored in the attributes field of the Participant object.
Endpoint: GET /api/v2/conversations/{conversationId}/participants/{participantId}
Scope: conversation:participant:read
First, we need to list participants in the conversation to find the correct participantId.
from genesyscloud.conversations import ConversationsApi
def get_participant_id(platform_client: PureCloudPlatformClientV2, conversation_id: str, user_id: str) -> str:
"""
Retrieves the participant ID for a specific user in a specific conversation.
Args:
platform_client: The authenticated Genesys Cloud client.
conversation_id: The ID of the conversation.
user_id: The ID of the user to find.
Returns:
The participant ID string.
"""
api_instance = ConversationsApi(platform_client)
try:
# Get all participants in the conversation
response = api_instance.get_conversations_participants(conversation_id)
for participant in response.entities:
if participant.user and participant.user.id == user_id:
return participant.id
raise ValueError(f"Participant with user ID {user_id} not found in conversation {conversation_id}")
except Exception as e:
print(f"Error retrieving participants: {e}")
raise
Now, retrieve the participant details including attributes.
def read_participant_attributes(platform_client: PureCloudPlatformClientV2, conversation_id: str, participant_id: str) -> dict:
"""
Reads the current attributes of a participant.
Args:
platform_client: The authenticated Genesys Cloud client.
conversation_id: The ID of the conversation.
participant_id: The ID of the participant.
Returns:
A dictionary of current attributes.
"""
api_instance = ConversationsApi(platform_client)
try:
# Get specific participant details
participant = api_instance.get_conversations_participants_by_id(
conversation_id,
participant_id
)
# Attributes are stored as a dictionary object in the response
# If no attributes exist, this may be None or an empty dict
current_attrs = participant.attributes if participant.attributes else {}
return current_attrs
except Exception as e:
print(f"Error reading participant attributes: {e}")
raise
Step 3: Update Participant Attributes
To write attributes, you must use a PATCH request. You cannot simply POST new attributes; you must update the existing participant object. The attributes field accepts a key-value JSON object.
Endpoint: PATCH /api/v2/conversations/{conversationId}/participants/{participantId}
Scope: conversation:participant:write
Important: The PATCH operation is additive for the attributes object. However, if you pass the entire attributes object, it will replace the existing attributes. To merge new attributes with existing ones, you must first read the existing attributes (as done in Step 2), update your local dictionary, and then send the complete updated dictionary back.
from genesyscloud.conversations.models import ParticipantPatchRequest
def update_participant_attributes(
platform_client: PureCloudPlatformClientV2,
conversation_id: str,
participant_id: str,
new_attributes: dict
) -> bool:
"""
Updates the attributes of a participant by merging new attributes with existing ones.
Args:
platform_client: The authenticated Genesys Cloud client.
conversation_id: The ID of the conversation.
participant_id: The ID of the participant.
new_attributes: A dictionary of new key-value pairs to add/update.
Returns:
True if successful.
"""
api_instance = ConversationsApi(platform_client)
# Step 1: Read existing attributes to preserve them
existing_attrs = read_participant_attributes(platform_client, conversation_id, participant_id)
# Step 2: Merge new attributes into existing ones
# This ensures we do not overwrite other attributes that may exist
merged_attrs = existing_attrs.copy()
merged_attrs.update(new_attributes)
# Step 3: Construct the PATCH request body
patch_body = ParticipantPatchRequest(
attributes=merged_attrs
)
try:
# Execute the PATCH request
api_instance.patch_conversations_participants_by_id(
conversation_id,
participant_id,
body=patch_body
)
print(f"Successfully updated attributes for participant {participant_id}")
return True
except Exception as e:
print(f"Error updating participant attributes: {e}")
# Handle specific HTTP errors if necessary
if hasattr(e, 'status') and e.status == 429:
print("Rate limited. Implement retry logic.")
raise
Complete Working Example
The following script combines all steps into a single runnable example. It finds an active voice conversation for a specific agent, reads their current attributes, adds a new attribute (external_system_id), and writes it back.
import os
import sys
from genesyscloud.purecloud import PureCloudPlatformClientV2
from genesyscloud.conversations import ConversationsApi
from genesyscloud.conversations.models import ConversationQueryRequest, ParticipantPatchRequest
def main():
# 1. Initialize Client
api_key_id = os.getenv("GENESYS_API_KEY_ID")
api_secret = os.getenv("GENESYS_API_SECRET")
if not api_key_id or not api_secret:
print("Error: GENESYS_API_KEY_ID and GENESYS_API_SECRET environment variables are required.")
sys.exit(1)
platform_client = PureCloudPlatformClientV2(
api_key_id=api_key_id,
api_secret=api_secret
)
# 2. Define Target User (Agent or Customer)
# Replace this with a valid user ID from your Genesys Cloud instance
target_user_id = os.getenv("TARGET_USER_ID", "12345678-1234-1234-1234-123456789012")
if target_user_id == "12345678-1234-1234-1234-123456789012":
print("Error: Please set TARGET_USER_ID environment variable to a valid Genesys Cloud User ID.")
sys.exit(1)
api_instance = ConversationsApi(platform_client)
try:
# 3. Find Active Voice Conversation
print(f"Searching for active voice conversations for user: {target_user_id}")
body = ConversationQueryRequest(
types=["voice"],
statuses=["active"],
user_ids=[target_user_id],
sort_order="desc"
)
response = api_instance.post_conversations_query(body=body)
if not response.entities or len(response.entities) == 0:
print("No active voice conversations found for this user.")
return
conversation_id = response.entities[0].id
print(f"Found conversation: {conversation_id}")
# 4. Find Participant ID
print(f"Retrieving participant details for conversation: {conversation_id}")
participants_response = api_instance.get_conversations_participants(conversation_id)
participant_id = None
for participant in participants_response.entities:
if participant.user and participant.user.id == target_user_id:
participant_id = participant.id
break
if not participant_id:
print(f"Could not find participant with user ID {target_user_id} in conversation {conversation_id}")
return
print(f"Found participant ID: {participant_id}")
# 5. Read Current Attributes
print("Reading current attributes...")
participant_detail = api_instance.get_conversations_participants_by_id(conversation_id, participant_id)
current_attrs = participant_detail.attributes if participant_detail.attributes else {}
print(f"Current attributes: {current_attrs}")
# 6. Prepare New Attributes
# Example: Adding an external reference ID
new_attrs = {
"external_system_id": "EXT-998877",
"customer_tier": "gold",
"last_interaction_source": "api_update"
}
# 7. Merge Attributes
merged_attrs = current_attrs.copy()
merged_attrs.update(new_attrs)
print(f"Merged attributes to write: {merged_attrs}")
# 8. Write Attributes
print("Updating participant attributes...")
patch_body = ParticipantPatchRequest(attributes=merged_attrs)
api_instance.patch_conversations_participants_by_id(
conversation_id,
participant_id,
body=patch_body
)
print("Success: Attributes updated.")
# 9. Verify Update (Optional)
print("Verifying update...")
final_participant = api_instance.get_conversations_participants_by_id(conversation_id, participant_id)
final_attrs = final_participant.attributes
print(f"Final attributes: {final_attrs}")
except Exception as e:
print(f"An error occurred: {e}")
if hasattr(e, 'status'):
print(f"HTTP Status: {e.status}")
print(f"Message: {e.message}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 401 Unauthorized
- Cause: The API Key ID or Secret is invalid, expired, or the token has expired.
- Fix: Verify that
GENESYS_API_KEY_IDandGENESYS_API_SECRETare correctly set in your environment. Ensure the API Key has not been revoked in the Genesys Cloud Admin console. - Code Check:
# Ensure credentials are loaded before initializing the client if not os.getenv("GENESYS_API_KEY_ID"): raise EnvironmentError("Missing API Key ID")
Error: 403 Forbidden
- Cause: The OAuth client does not have the required scopes.
- Fix: Go to the Genesys Cloud Admin console > Platform > API Keys. Select your API Key and ensure it has the following scopes:
conversation:participant:readconversation:participant:writeconversation:view
- Note: If you are using a JWT grant, ensure the JWT issuer has the necessary permissions.
Error: 404 Not Found
- Cause: The
conversation_idorparticipant_idis invalid or the conversation has ended. - Fix: Verify that the conversation is still active. If the conversation ends, the participant object may still exist but will not be modifiable in the same way. Ensure you are using the correct
conversation_idfrom the query response. - Debugging:
# Log the IDs before making the PATCH request print(f"DEBUG: Conversation ID: {conversation_id}") print(f"DEBUG: Participant ID: {participant_id}")
Error: 429 Too Many Requests
- Cause: You have exceeded the API rate limits.
- Fix: Implement exponential backoff retry logic. The Genesys Cloud SDK does not automatically retry 429 errors in all versions, so manual handling is recommended.
- Code Example:
import time def patch_with_retry(api_instance, conversation_id, participant_id, body, max_retries=3): for attempt in range(max_retries): try: api_instance.patch_conversations_participants_by_id(conversation_id, participant_id, body=body) return except Exception as e: if hasattr(e, 'status') and e.status == 429: wait_time = 2 ** attempt print(f"Rate limited. Retrying in {wait_time} seconds...") time.sleep(wait_time) else: raise raise Exception("Max retries exceeded for 429 error")
Error: Attributes Not Persisting
- Cause: You are overwriting the entire
attributesobject with a partial dictionary, losing existing data, or you are not sending theattributesfield in theParticipantPatchRequest. - Fix: Always read the existing attributes, merge your new data, and send the complete dictionary. Ensure the
ParticipantPatchRequestexplicitly sets theattributesparameter. - Code Check:
# Incorrect: Only sending new attributes, which may overwrite others depending on server behavior # patch_body = ParticipantPatchRequest(attributes=new_attrs) # Correct: Merging first merged_attrs = existing_attrs.copy() merged_attrs.update(new_attrs) patch_body = ParticipantPatchRequest(attributes=merged_attrs)