Updating Participant Attributes Mid-Conversation via the Genesys Cloud API
What You Will Build
- You will build a script that identifies an active conversation participant and updates their custom attributes in real time without ending the session.
- This tutorial uses the Genesys Cloud Conversations API (
/api/v2/conversations) and the Python SDK (genesys-cloud-sdk). - The code examples are written in Python 3.9+ using the
requestslibrary for direct HTTP calls and the official SDK for structured interactions.
Prerequisites
- OAuth Client Type: Private Key JWT or Client Credentials grant.
- Required Scopes:
conversation:participant:write(Required to update participant attributes)conversation:view(Required to retrieve the current conversation state)user:read(Optional, if you need to resolve user IDs for routing contexts)
- SDK Version:
genesys-cloud-sdkversion 100.0.0 or higher. - Runtime: Python 3.9 or higher.
- Dependencies:
pip install genesys-cloud-sdk requests pyjwt cryptography - Environment Variables: You must have your
GENESYS_CLOUD_REGION,GENESYS_CLOUD_CLIENT_ID,GENESYS_CLOUD_PRIVATE_KEY_FILE(or PEM content), andGENESYS_CLOUD_ORG_IDconfigured.
Authentication Setup
The Genesys Cloud API requires a valid OAuth 2.0 Bearer token for every request. The token expires after 15 minutes. For production-grade scripts, you must implement token caching and automatic refresh logic. The following example uses the genesys-cloud-sdk built-in authentication manager, which handles refresh tokens automatically.
import os
import sys
from purecloud_platform_client_v2 import (
PureCloudPlatformClientV2,
Configuration,
OAuthClient,
PrivateKeyJwtFlow
)
def get_authenticated_client() -> PureCloudPlatformClientV2:
"""
Initializes and authenticates the Genesys Cloud Platform Client.
Handles Private Key JWT flow automatically.
"""
region = os.environ.get("GENESYS_CLOUD_REGION", "us-east-1")
client_id = os.environ.get("GENESYS_CLOUD_CLIENT_ID")
private_key_path = os.environ.get("GENESYS_CLOUD_PRIVATE_KEY_FILE")
if not client_id or not private_key_path:
raise ValueError("Missing required environment variables: GENESYS_CLOUD_CLIENT_ID, GENESYS_CLOUD_PRIVATE_KEY_FILE")
# Load the private key from the file system
with open(private_key_path, "r") as key_file:
private_key = key_file.read()
# Configure the client
config = Configuration(
host=f"https://api.{region}.mypurecloud.com",
region=region
)
# Initialize the OAuth flow
oauth = OAuthClient(
client_id=client_id,
private_key=private_key,
config=config
)
# Authenticate and get the token
# This caches the token internally and refreshes it automatically when expired
oauth.authenticate()
# Create the platform client instance
platform_client = PureCloudPlatformClientV2(config)
return platform_client
if __name__ == "__main__":
try:
client = get_authenticated_client()
print("Authentication successful.")
except Exception as e:
print(f"Authentication failed: {e}")
sys.exit(1)
Implementation
Step 1: Locate the Active Conversation
Before updating attributes, you must identify the specific conversation and the participant within it. Genesys Cloud conversations are identified by a UUID. If you do not have this ID, you must query the active conversations for a specific user or queue.
The following code retrieves the most recent active conversation for a given user ID.
Required Scope: conversation:view
from purecloud_platform_client_v2.api import conversations_api
from purecloud_platform_client_v2.rest import ApiException
def get_active_conversation(user_id: str, platform_client: PureCloudPlatformClientV2) -> str | None:
"""
Finds the most recent active conversation for a specific user.
Args:
user_id: The UUID of the user (agent).
platform_client: The authenticated client instance.
Returns:
The conversation ID string, or None if no active conversation exists.
"""
api_instance = conversations_api.ConversationsApi(platform_client)
# Query parameters
# type='voice' ensures we only look at voice calls. Change to 'chat' or 'email' as needed.
# status='active' filters for ongoing conversations.
query_params = {
"type": "voice",
"status": "active",
"pageSize": 1,
"sortBy": "startTime",
"sortOrder": "desc"
}
try:
# Get conversations for a specific user
response = api_instance.get_conversations_users_conversations(
user_id=user_id,
**query_params
)
if response.entities and len(response.entities) > 0:
conversation_id = response.entities[0].id
print(f"Found active conversation: {conversation_id}")
return conversation_id
else:
print(f"No active voice conversations found for user {user_id}.")
return None
except ApiException as e:
print(f"Exception when calling ConversationsApi->get_conversations_users_conversations: {e}")
if e.status == 404:
print("User not found or no conversations exist.")
elif e.status == 401 or e.status == 403:
print("Authentication or Authorization failed. Check scopes.")
raise e
# Usage Example
# conv_id = get_active_conversation("user-uuid-here", client)
Step 2: Retrieve Current Participant State
Genesys Cloud uses an optimistic concurrency control model for participant updates. To update a participant’s attributes, you must provide the current version of that participant. If you submit an update with an outdated version, the API returns a 409 Conflict.
You must first retrieve the full conversation object to extract the participant’s ID and current version.
Required Scope: conversation:view
from purecloud_platform_client_v2.api import conversations_api
from purecloud_platform_client_v2.rest import ApiException
def get_participant_info(conversation_id: str, platform_client: PureCloudPlatformClientV2) -> dict | None:
"""
Retrieves the conversation details to find the participant ID and version.
Args:
conversation_id: The UUID of the conversation.
platform_client: The authenticated client instance.
Returns:
A dictionary containing 'participant_id', 'version', and 'attributes'.
Returns None if the conversation is not found.
"""
api_instance = conversations_api.ConversationsApi(platform_client)
try:
# Fetch the full conversation object
response = api_instance.get_conversations_conversation(
conversation_id=conversation_id
)
# Identify the participant.
# For voice, usually the agent is the first participant with type 'agent'.
# Adjust this logic based on your specific flow (e.g., finding by external_contact_id).
target_participant = None
for participant in response.participants:
if participant.type == "agent": # Or check participant.external_contact_id if targeting a customer
target_participant = participant
break
if target_participant:
return {
"participant_id": target_participant.id,
"version": target_participant.version,
"current_attributes": target_participant.attributes
}
else:
print("No suitable participant found in the conversation.")
return None
except ApiException as e:
print(f"Exception when calling ConversationsApi->get_conversations_conversation: {e}")
if e.status == 404:
print("Conversation not found. It may have ended.")
elif e.status == 401 or e.status == 403:
print("Authentication or Authorization failed.")
raise e
# Usage Example
# info = get_participant_info(conv_id, client)
# if info:
# print(f"Participant ID: {info['participant_id']}, Version: {info['version']}")
Step 3: Update Participant Attributes
Now that you have the participant_id and the version, you can issue a PUT request to update the attributes.
Critical Note: The PUT method replaces the entire attributes object. If you only want to add a new attribute, you must merge the new key-value pair with the existing attributes dictionary before sending the request. If you do not merge, existing attributes will be deleted.
Required Scope: conversation:participant:write
from purecloud_platform_client_v2.model import ParticipantPatch
from purecloud_platform_client_v2.api import conversations_api
from purecloud_platform_client_v2.rest import ApiException
def update_participant_attributes(
conversation_id: str,
participant_id: str,
version: int,
new_attributes: dict,
platform_client: PureCloudPlatformClientV2
) -> bool:
"""
Updates the attributes of a specific participant in a conversation.
Args:
conversation_id: The UUID of the conversation.
participant_id: The UUID of the participant to update.
version: The current version of the participant (required for concurrency control).
new_attributes: The COMPLETE set of attributes to set.
Note: This replaces existing attributes.
platform_client: The authenticated client instance.
Returns:
True if successful, False otherwise.
"""
api_instance = conversations_api.ConversationsApi(platform_client)
# Construct the patch object
# The 'attributes' field expects a dictionary of strings to strings/numbers/booleans
participant_patch = ParticipantPatch(
attributes=new_attributes,
version=version
)
try:
# Execute the update
# Note: The SDK method name is update_conversations_conversation_participant
api_instance.update_conversations_conversation_participant(
conversation_id=conversation_id,
participant_id=participant_id,
body=participant_patch
)
print(f"Successfully updated attributes for participant {participant_id}.")
return True
except ApiException as e:
print(f"Exception when calling update_conversations_conversation_participant: {e}")
if e.status == 409:
print("Conflict: The participant version has changed since you retrieved it. Please re-fetch and retry.")
elif e.status == 404:
print("Not Found: Conversation or participant does not exist.")
elif e.status == 400:
print("Bad Request: Check the structure of your attributes or version number.")
elif e.status == 429:
print("Rate Limited: Too many requests. Implement exponential backoff.")
return False
# Usage Example
# Assuming 'info' from Step 2
# current_attrs = info['current_attributes'] if info['current_attributes'] else {}
# current_attrs['order_status'] = 'processing'
# current_attrs['priority'] = 'high'
#
# update_participant_attributes(
# conversation_id=conv_id,
# participant_id=info['participant_id'],
# version=info['version'],
# new_attributes=current_attrs,
# platform_client=client
# )
Complete Working Example
This script combines authentication, conversation lookup, attribute merging, and updating into a single executable module. It includes retry logic for 429 errors and version conflict handling.
import os
import sys
import time
from purecloud_platform_client_v2 import (
PureCloudPlatformClientV2,
Configuration,
OAuthClient,
PrivateKeyJwtFlow
)
from purecloud_platform_client_v2.api import conversations_api
from purecloud_platform_client_v2.model import ParticipantPatch
from purecloud_platform_client_v2.rest import ApiException
class GenesysConversationManager:
def __init__(self):
self.client = self._authenticate()
def _authenticate(self) -> PureCloudPlatformClientV2:
region = os.environ.get("GENESYS_CLOUD_REGION", "us-east-1")
client_id = os.environ.get("GENESYS_CLOUD_CLIENT_ID")
private_key_path = os.environ.get("GENESYS_CLOUD_PRIVATE_KEY_FILE")
if not client_id or not private_key_path:
raise ValueError("Missing required environment variables.")
with open(private_key_path, "r") as key_file:
private_key = key_file.read()
config = Configuration(host=f"https://api.{region}.mypurecloud.com", region=region)
oauth = OAuthClient(client_id=client_id, private_key=private_key, config=config)
oauth.authenticate()
return PureCloudPlatformClientV2(config)
def get_active_conversation(self, user_id: str) -> str | None:
api_instance = conversations_api.ConversationsApi(self.client)
try:
response = api_instance.get_conversations_users_conversations(
user_id=user_id,
type="voice",
status="active",
pageSize=1,
sortBy="startTime",
sortOrder="desc"
)
if response.entities and len(response.entities) > 0:
return response.entities[0].id
except ApiException as e:
print(f"Error fetching conversation: {e}")
return None
def update_attributes(self, conversation_id: str, participant_id: str, version: int, attributes: dict, max_retries: int = 3) -> bool:
api_instance = conversations_api.ConversationsApi(self.client)
participant_patch = ParticipantPatch(attributes=attributes, version=version)
for attempt in range(max_retries):
try:
api_instance.update_conversations_conversation_participant(
conversation_id=conversation_id,
participant_id=participant_id,
body=participant_patch
)
print("Attributes updated successfully.")
return True
except ApiException as e:
if e.status == 409:
print(f"Version conflict. Attempt {attempt + 1} to re-fetch and retry...")
# In a real scenario, you would re-fetch the participant version here
# For this example, we simulate a delay and assume the version might have changed
# Ideally, you would call get_participant_info again inside this block
time.sleep(1)
# Note: In a production loop, you must re-fetch the version from the API here
# before retrying, otherwise the 409 will persist.
return False
elif e.status == 429:
wait_time = 2 ** attempt
print(f"Rate limited. Waiting {wait_time} seconds...")
time.sleep(wait_time)
else:
print(f"Unexpected error: {e}")
return False
return False
def main():
# Configuration
USER_ID = os.environ.get("GENESYS_CLOUD_USER_ID") # The agent's user ID
if not USER_ID:
print("Please set GENESYS_CLOUD_USER_ID environment variable.")
sys.exit(1)
manager = GenesysConversationManager()
# Step 1: Find Conversation
conv_id = manager.get_active_conversation(USER_ID)
if not conv_id:
print("No active conversation found.")
sys.exit(0)
print(f"Processing Conversation: {conv_id}")
# Step 2: Get Participant Info
api_instance = conversations_api.ConversationsApi(manager.client)
try:
conv_details = api_instance.get_conversations_conversation(conversation_id=conv_id)
target_participant = None
for p in conv_details.participants:
if p.type == "agent":
target_participant = p
break
if not target_participant:
print("No agent participant found.")
sys.exit(1)
current_attrs = target_participant.attributes if target_participant.attributes else {}
participant_id = target_participant.id
version = target_participant.version
# Step 3: Merge New Data
# Example: Adding a custom attribute
current_attrs['internal_note'] = 'Customer requested callback'
current_attrs['priority_level'] = 'high'
print(f"Updating participant {participant_id} with version {version}")
# Step 4: Update
success = manager.update_attributes(
conversation_id=conv_id,
participant_id=participant_id,
version=version,
attributes=current_attrs
)
if not success:
print("Update failed after retries.")
except ApiException as e:
print(f"Failed to retrieve conversation details: {e}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 409 Conflict (Version Mismatch)
- What causes it: You retrieved the participant version at time T1, but another system (or the same system) updated the participant at time T2. Your update request still contains the version from T1.
- How to fix it: Implement a retry loop. When you receive a 409, immediately call
get_conversations_conversationagain to fetch the latest version, merge your new attributes with the latest attributes, and retry the update with the new version. - Code Fix:
# Inside your update function loop if e.status == 409: # Re-fetch latest state latest_info = get_participant_info(conversation_id, platform_client) if latest_info: latest_version = latest_info['version'] latest_attrs = latest_info['current_attributes'] or {} # Merge your new data into the latest state latest_attrs['your_new_key'] = 'your_new_value' # Retry with new version and merged attributes return update_participant_attributes( conversation_id, latest_info['participant_id'], latest_version, latest_attrs, platform_client ) else: raise Exception("Conversation ended or participant removed during retry.")
Error: 403 Forbidden (Insufficient Scopes)
- What causes it: The OAuth token does not include
conversation:participant:write. - How to fix it: Check your OAuth client configuration in the Genesys Cloud Admin console. Ensure the “Private Key” or “Client Credentials” grant has the
conversation:participant:writescope checked. Regenerate the token.
Error: 400 Bad Request (Invalid Attributes)
- What causes it: The
attributesobject contains invalid data types (e.g., nested objects or arrays, which are not supported in participant attributes) or keys that exceed length limits. - How to fix it: Participant attributes must be flat key-value pairs. Values must be strings, numbers, or booleans. Keys must be alphanumeric or underscores.
// Valid { "order_id": "12345", "is_vip": true } // Invalid { "details": { "nested": "object" } // Nested objects are not allowed }
Error: 429 Too Many Requests
- What causes it: You are exceeding the rate limit for the Conversations API (typically 100 requests per second for this endpoint, but varies by org tier).
- How to fix it: Implement exponential backoff. Do not retry immediately. Wait for
2^attemptseconds.