Updating Participant Attributes Mid-Session via the Conversations API
What You Will Build
- You will build a script that locates an active conversation and updates the specific attributes of a participant in real time.
- This tutorial uses the Genesys Cloud CX Conversations API (
/api/v2/conversations) and the PureCloud Platform Client V2 SDK. - The implementation is demonstrated in Python using the
requestslibrary for raw HTTP control and the official Python SDK for type safety.
Prerequisites
- OAuth Client Type: A Genesys Cloud OAuth Client with the
client_credentialsgrant type. - Required Scopes:
conversation:participant:write(Required to update participant data)conversation:read(Required to locate the conversation if ID is not known)
- SDK Version:
genesys-cloud-purecloud-platform-client>= 140.0.0 - Runtime Requirements: Python 3.9+
- External Dependencies:
requests(for raw HTTP examples)genesys-cloud-purecloud-platform-client(for SDK examples)
Authentication Setup
Genesys Cloud APIs require an OAuth 2.0 bearer token. For server-to-server integrations, the client_credentials flow is standard. You must cache the token and handle expiration, as tokens are valid for one hour.
The following function demonstrates retrieving a token using the requests library. This pattern is identical for both the raw API and SDK examples, though the SDK handles this internally if configured correctly.
import requests
import time
from typing import Optional
# Configuration
GENESYS_CLOUD_REGION = "mypurecloud.com" # Or your specific region: e.g., "usw2.pure.cloud"
CLIENT_ID = "your_client_id"
CLIENT_SECRET = "your_client_secret"
def get_access_token() -> str:
"""
Retrieves an OAuth 2.0 access token from Genesys Cloud.
In production, implement caching to avoid calling this on every request.
"""
url = f"https://{GENESYS_CLOUD_REGION}/oauth/token"
payload = {
"grant_type": "client_credentials",
"scope": "conversation:participant:write conversation:read"
}
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
# Basic Auth header for client credentials
auth_response = requests.post(
url,
data=payload,
headers=headers,
auth=(CLIENT_ID, CLIENT_SECRET)
)
if auth_response.status_code != 200:
raise Exception(f"Failed to obtain token: {auth_response.status_code} - {auth_response.text}")
return auth_response.json()["access_token"]
Implementation
Step 1: Locate the Active Conversation
To update a participant, you need the conversationId. If you do not have this ID, you must query the Conversations API. The GET /api/v2/conversations endpoint returns a list of active conversations.
Endpoint: GET /api/v2/conversations
Scope: conversation:read
The response includes a pageSize and nextPageUri. For a tutorial, we will fetch the first page. In production, you must iterate through pages if your queue is large.
def get_active_conversations(token: str) -> list:
"""
Retrieves a list of active conversations.
"""
url = f"https://{GENESYS_CLOUD_REGION}/api/v2/conversations"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
if response.status_code != 200:
raise Exception(f"Failed to fetch conversations: {response.status_code} - {response.text}")
return response.json()["entities"]
Step 2: Identify the Target Participant
Once you have the conversation entity, you must inspect the participants array to find the specific user or system participant you intend to update. Each participant has a participantId (UUID).
Critical Note: You cannot update attributes for a participant if that participant is not currently in the ACTIVE or RESERVED state for that conversation. Attempting to update a participant who has already left (COMPLETED) will result in a 404 or 409 error.
The following function filters participants by a specific identifier, such as an external ID or address (e.g., phone number or email).
def find_participant_in_conversation(conversation: dict, target_address: str) -> Optional[str]:
"""
Finds the participantId for a specific address within a conversation.
Args:
conversation: The conversation entity from the API.
target_address: The phone number, email, or external ID to match.
Returns:
The participantId (UUID) if found, else None.
"""
participants = conversation.get("participants", [])
for p in participants:
# Check both the address (phone/email) and external identifiers
if p.get("address") == target_address:
return p.get("participantId")
# Check external identifiers if set
external_identifiers = p.get("externalIdentifiers", [])
for ext_id in external_identifiers:
if ext_id.get("externalId") == target_address:
return p.get("participantId")
return None
Step 3: Update Participant Attributes via Raw HTTP
The primary method for updating participant data is the PATCH request to the specific participant endpoint.
Endpoint: PATCH /api/v2/conversations/{conversationId}/participants/{participantId}
Scope: conversation:participant:write
Request Body Structure:
The body must be a JSON object containing the attributes field. The attributes field is a map of key-value pairs. You can add, update, or delete keys. To delete a key, set its value to null.
def update_participant_attributes_raw(
token: str,
conversation_id: str,
participant_id: str,
new_attributes: dict
) -> dict:
"""
Updates participant attributes using raw HTTP PATCH request.
Args:
token: OAuth Bearer token.
conversation_id: UUID of the conversation.
participant_id: UUID of the participant.
new_attributes: Dict of key-value pairs to set.
Returns:
The updated participant entity.
"""
url = f"https://{GENESYS_CLOUD_REGION}/api/v2/conversations/{conversation_id}/participants/{participant_id}"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# The API expects the attributes to be nested under "attributes"
payload = {
"attributes": new_attributes
}
response = requests.patch(
url,
json=payload,
headers=headers
)
# Handle Rate Limiting (429)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 1))
print(f"Rate limited. Retrying in {retry_after} seconds...")
time.sleep(retry_after)
# In a production system, wrap this in a retry loop
return update_participant_attributes_raw(token, conversation_id, participant_id, new_attributes)
# Handle Not Found (404) - Participant may have left
if response.status_code == 404:
raise Exception(f"Participant {participant_id} not found in conversation {conversation_id}. They may have ended the call.")
# Handle Forbidden (403) - Missing Scopes
if response.status_code == 403:
raise Exception("Access denied. Ensure the OAuth client has 'conversation:participant:write' scope.")
if response.status_code not in [200, 204]:
raise Exception(f"Update failed: {response.status_code} - {response.text}")
# 204 No Content is common for PATCH if no body is returned, but Genesys often returns 200 with entity
if response.status_code == 204:
return {"status": "updated"}
return response.json()
Step 4: Update Participant Attributes via Official SDK
Using the SDK provides type safety and automatic serialization of complex objects. The PureCloudPlatformClientV2 library maps directly to the API.
SDK Class: ConversationApi
Method: post_conversations_participant_attributes (Note: Despite the name post, this endpoint often uses POST for complex attribute merges in some SDK versions, but patch is the standard REST verb. In the Python SDK, the method is patch_conversations_participant or similar depending on version. As of v140+, the method is patch_conversations_participant).
Correction: In the current Genesys Cloud Python SDK, the method to update a participant is patch_conversations_participant.
from purecloudplatformclientv2 import (
Configuration,
ApiClient,
ConversationApi,
PatchParticipantRequest,
ParticipantAttributes
)
def update_participant_attributes_sdk(
config: Configuration,
conversation_id: str,
participant_id: str,
new_attributes: dict
) -> dict:
"""
Updates participant attributes using the Genesys Cloud Python SDK.
Args:
config: PureCloudPlatformClientV2 Configuration object.
conversation_id: UUID of the conversation.
participant_id: UUID of the participant.
new_attributes: Dict of key-value pairs to set.
"""
# Create the API client
with ApiClient(configuration=config) as api_client:
conversation_api = ConversationApi(api_client)
# Construct the request body
# The SDK requires specific model objects
attributes_model = ParticipantAttributes(attributes=new_attributes)
patch_request = PatchParticipantRequest(
attributes=attributes_model
)
try:
# Execute the patch request
# The SDK returns a Participant object
result = conversation_api.patch_conversations_participant(
conversation_id=conversation_id,
participant_id=participant_id,
body=patch_request
)
return result.to_dict()
except Exception as e:
# The SDK wraps HTTP errors in specific exceptions
print(f"SDK Error: {e}")
raise
Complete Working Example
This script combines authentication, conversation lookup, participant identification, and attribute updating into a single runnable flow. It uses the raw requests approach for transparency, but the logic applies equally to the SDK.
import requests
import time
import json
import sys
# --- Configuration ---
GENESYS_CLOUD_REGION = "mypurecloud.com" # Change to your region
CLIENT_ID = "your_client_id"
CLIENT_SECRET = "your_client_secret"
TARGET_PHONE_NUMBER = "+15551234567" # The phone number of the participant to update
# --- Helper Functions ---
def get_token() -> str:
url = f"https://{GENESYS_CLOUD_REGION}/oauth/token"
payload = {
"grant_type": "client_credentials",
"scope": "conversation:participant:write conversation:read"
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
resp = requests.post(url, data=payload, headers=headers, auth=(CLIENT_ID, CLIENT_SECRET))
if resp.status_code != 200:
raise Exception(f"Auth failed: {resp.text}")
return resp.json()["access_token"]
def find_conversation_and_participant(token: str) -> tuple:
"""
Finds the first active conversation containing the target phone number.
Returns (conversation_id, participant_id) or (None, None).
"""
url = f"https://{GENESYS_CLOUD_REGION}/api/v2/conversations"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# Fetch first page of conversations
resp = requests.get(url, headers=headers)
if resp.status_code != 200:
raise Exception(f"Failed to get conversations: {resp.text}")
entities = resp.json().get("entities", [])
for conv in entities:
participants = conv.get("participants", [])
for p in participants:
if p.get("address") == TARGET_PHONE_NUMBER:
return conv.get("id"), p.get("participantId")
return None, None
def update_attributes(token: str, conv_id: str, part_id: str) -> bool:
"""
Updates the participant attributes with a timestamp and a custom flag.
"""
url = f"https://{GENESYS_CLOUD_REGION}/api/v2/conversations/{conv_id}/participants/{part_id}"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# Define the attributes to add/update
current_time = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
new_attributes = {
"last_updated_by_script": current_time,
"priority_level": "high",
"custom_flag": True
}
payload = {
"attributes": new_attributes
}
print(f"Updating participant {part_id} in conversation {conv_id}...")
print(f"Payload: {json.dumps(payload, indent=2)}")
resp = requests.patch(url, json=payload, headers=headers)
if resp.status_code == 204:
print("Success: Attributes updated (No Content returned).")
return True
elif resp.status_code == 200:
print(f"Success: Attributes updated. Response: {resp.json()}")
return True
else:
print(f"Failed: {resp.status_code} - {resp.text}")
return False
# --- Main Execution ---
def main():
try:
# 1. Authenticate
print("Step 1: Authenticating...")
token = get_token()
print("Auth successful.")
# 2. Find Target
print(f"Step 2: Searching for participant with phone: {TARGET_PHONE_NUMBER}")
conv_id, part_id = find_conversation_and_participant(token)
if not conv_id or not part_id:
print("No active conversation found for the target phone number.")
sys.exit(0)
print(f"Found Conversation ID: {conv_id}")
print(f"Found Participant ID: {part_id}")
# 3. Update Attributes
print("Step 3: Updating attributes...")
success = update_attributes(token, conv_id, part_id)
if success:
print("Operation completed successfully.")
else:
print("Operation failed.")
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden
Cause: The OAuth client used to generate the token lacks the conversation:participant:write scope.
Fix:
- Go to the Genesys Cloud Admin Portal.
- Navigate to Setup > Applications > API Integration.
- Select your client.
- Under Scopes, ensure
conversation:participant:writeis checked. - Generate a new token.
Error: 404 Not Found
Cause: The conversationId or participantId is incorrect, or the participant has already left the conversation.
Fix:
- Verify the IDs are valid UUIDs.
- Check the participant’s
state. If the state isCOMPLETED, you cannot update attributes. - If updating a system-generated participant (e.g., a bot), ensure the bot is still active in the conversation.
Error: 409 Conflict
Cause: Attempting to update a participant in a way that conflicts with current conversation state (rare for attributes, more common for state changes).
Fix: Ensure you are not trying to update a participant who is in a COMPLETED state.
Error: 429 Too Many Requests
Cause: You have exceeded the rate limit for the Conversations API.
Fix:
- Check the
Retry-Afterheader in the response. - Implement exponential backoff in your retry logic.
- Batch updates if you are processing many conversations.