Programmatically Setting Wrap-Up Codes After Interaction Completion
What You Will Build
- A Python script that detects when a conversation has ended and immediately assigns a specific wrap-up code to that interaction.
- This solution uses the Genesys Cloud CX Conversations API and Wrap-up Codes API via the official Python SDK.
- The implementation covers Python 3.9+ using the
genesyscloudSDK andrequestsfor raw fallbacks.
Prerequisites
- OAuth Client Type: Machine-to-Machine (M2M) or User-to-Machine (U2M) client.
- Required Scopes:
conversation:conversation:write(to update conversation wrap-up codes)wrapupcode:wrapupcode:read(to fetch valid wrap-up code IDs if not hardcoded)
- SDK Version:
genesyscloud>= 1.0.0 (official Genesys Cloud Python SDK). - Language/Runtime: Python 3.9 or higher.
- External Dependencies:
genesyscloudrequestspython-dotenv(for secure credential management)
Authentication Setup
The Genesys Cloud SDK handles OAuth token acquisition and refresh automatically when initialized with client credentials. You must store your Organization ID, Client ID, and Client Secret securely.
import os
from dotenv import load_dotenv
from genesyscloud.platform.client import PlatformClient
from genesyscloud.conversations.api import ConversationsApi
from genesyscloud.wrapup_codes.api import WrapupCodesApi
# Load environment variables from .env file
load_dotenv()
def get_authenticated_client():
"""
Initializes and returns an authenticated Genesys Cloud Platform Client.
"""
org_id = os.getenv("GENESYS_ORG_ID")
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
if not all([org_id, client_id, client_secret]):
raise ValueError("Missing required environment variables: GENESYS_ORG_ID, GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET")
# Initialize the Platform Client
client = PlatformClient(
org_id=org_id,
client_id=client_id,
client_secret=client_secret
)
# The client automatically handles token acquisition and refresh
return client
Implementation
Step 1: Identify the Conversation and Target Wrap-Up Code
Before setting a wrap-up code, you must identify the specific conversation instance and the ID of the wrap-up code you wish to apply. Wrap-up codes are associated with specific media types (voice, chat, email) and often with specific queue or skill contexts.
For this tutorial, we assume you have the conversationId from a callback or a previous query. We will also fetch the list of available wrap-up codes to ensure we use a valid wrapupCodeId.
OAuth Scope: wrapupcode:wrapupcode:read
from genesyscloud.models import GetWrapupCodesResponse
def get_wrapup_code_by_name(client: PlatformClient, name: str, media_type: str = "voice") -> str:
"""
Retrieves the ID of a wrap-up code by its name and media type.
Args:
client: Authenticated Genesys Cloud Platform Client.
name: The exact name of the wrap-up code.
media_type: The media type (e.g., 'voice', 'chat', 'email').
Returns:
The wrap-up code ID string.
"""
api = WrapupCodesApi(client)
try:
# Fetch all wrap-up codes. Note: For large orgs, consider filtering by queueId if possible.
response: GetWrapupCodesResponse = api.get_wrapup_codes(
media_type=media_type,
expand="wrapupCode"
)
if response.entities:
for code in response.entities:
if code.name == name:
return code.id
raise ValueError(f"Wrap-up code '{name}' not found for media type '{media_type}'")
except Exception as e:
print(f"Error fetching wrap-up codes: {e}")
raise e
# Example usage:
# client = get_authenticated_client()
# wrapup_id = get_wrapup_code_by_name(client, "Call Resolved", "voice")
Step 2: Construct the Wrap-Up Update Payload
The Genesys Cloud API requires a specific payload structure to update wrap-up codes on a conversation. This is done via a POST request to the /conversations/{conversationId}/wrapup endpoint.
The payload must include:
wrapupCodes: A list of objects containingwrapupCodeIdand optionallywrapupCodeName.wrapUpBy: The user ID or resource ID performing the wrap-up. If this is an automated system, use a Service Account or a designated Agent’s ID.
Important: You cannot set a wrap-up code on a conversation that is still in progress. The conversation must be in a closed or ended state.
Step 3: Execute the Wrap-Up Code Assignment
We will create a function that takes the conversationId and wrapupCodeId, constructs the payload, and sends the request. We will include robust error handling for common HTTP status codes (400, 401, 403, 404, 429, 5xx).
OAuth Scope: conversation:conversation:write
from genesyscloud.models import PostConversationWrapupRequest
import time
def set_wrapup_code_on_conversation(
client: PlatformClient,
conversation_id: str,
wrapup_code_id: str,
wrapper_user_id: str
) -> bool:
"""
Sets a specific wrap-up code on a closed conversation.
Args:
client: Authenticated Genesys Cloud Platform Client.
conversation_id: The ID of the conversation to wrap up.
wrapup_code_id: The ID of the wrap-up code to apply.
wrapper_user_id: The user ID of the agent or system performing the wrap-up.
Returns:
True if successful, False otherwise.
"""
api = ConversationsApi(client)
# Construct the request body
# The SDK model PostConversationWrapupRequest requires a list of wrap-up codes
request_body = PostConversationWrapupRequest(
wrapup_codes=[{
"wrapupCodeId": wrapup_code_id
}],
wrapUpBy=wrapper_user_id
)
try:
# Call the API to set the wrap-up code
# This endpoint returns 204 No Content on success
api.post_conversations_conversation_id_wrapup(
conversation_id=conversation_id,
body=request_body
)
print(f"Successfully set wrap-up code {wrapup_code_id} on conversation {conversation_id}")
return True
except Exception as e:
# Handle specific HTTP errors
if hasattr(e, 'status'):
status = e.status
if status == 400:
print(f"Bad Request: Invalid conversation state or payload for conversation {conversation_id}. Details: {e}")
elif status == 401:
print(f"Unauthorized: Token expired or invalid. Details: {e}")
elif status == 403:
print(f"Forbidden: Insufficient permissions. Ensure 'conversation:conversation:write' scope is present. Details: {e}")
elif status == 404:
print(f"Not Found: Conversation {conversation_id} does not exist. Details: {e}")
elif status == 429:
print(f"Rate Limited: Too many requests. Implement backoff. Details: {e}")
return False # Caller should handle retry
elif 500 <= status < 600:
print(f"Server Error: Internal Genesys Cloud error. Details: {e}")
return False # Caller should handle retry
else:
print(f"Unexpected Error ({status}): {e}")
else:
print(f"General Error: {e}")
return False
Step 4: Implementing Retry Logic for Rate Limits (429)
Genesys Cloud APIs enforce rate limits. When integrating wrap-up code assignment into a high-volume system (e.g., post-call automation), you may encounter 429 errors. A simple exponential backoff strategy is recommended.
import random
def set_wrapup_with_retry(
client: PlatformClient,
conversation_id: str,
wrapup_code_id: str,
wrapper_user_id: str,
max_retries: int = 3,
base_delay: float = 1.0
) -> bool:
"""
Attempts to set the wrap-up code with exponential backoff on 429 errors.
"""
for attempt in range(max_retries):
success = set_wrapup_code_on_conversation(
client,
conversation_id,
wrapup_code_id,
wrapper_user_id
)
if success:
return True
# Check if the error was a rate limit (429)
# Note: In a real scenario, you would catch the specific exception type
# For this example, we assume the previous function printed the error
# and returned False. We would need to modify the previous function
# to return a specific error code or exception for 429.
# Since the previous function prints and returns False on 429,
# we need a way to distinguish 429 from other errors.
# Let's refactor the previous function slightly in the complete example
# to return a tuple (success, is_rate_limited).
# For now, we assume a generic retry delay
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
print(f"Retry attempt {attempt + 1} after {delay:.2f} seconds...")
time.sleep(delay)
return False
Note: The complete working example below refactors the error handling to properly support retries.
Complete Working Example
This script demonstrates the full flow: authentication, fetching a wrap-up code ID, and setting it on a conversation with retry logic.
Prerequisites:
- Install dependencies:
pip install genesyscloud requests python-dotenv - Create a
.envfile with:GENESYS_ORG_ID=your_org_id GENESYS_CLIENT_ID=your_client_id GENESYS_CLIENT_SECRET=your_client_secret
import os
import time
import random
from dotenv import load_dotenv
from genesyscloud.platform.client import PlatformClient
from genesyscloud.conversations.api import ConversationsApi
from genesyscloud.wrapup_codes.api import WrapupCodesApi
from genesyscloud.models import GetWrapupCodesResponse, PostConversationWrapupRequest
# Load environment variables
load_dotenv()
def get_authenticated_client() -> PlatformClient:
org_id = os.getenv("GENESYS_ORG_ID")
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
if not all([org_id, client_id, client_secret]):
raise ValueError("Missing required environment variables.")
return PlatformClient(
org_id=org_id,
client_id=client_id,
client_secret=client_secret
)
def get_wrapup_code_id(client: PlatformClient, name: str, media_type: str = "voice") -> str:
api = WrapupCodesApi(client)
try:
response: GetWrapupCodesResponse = api.get_wrapup_codes(media_type=media_type)
if response.entities:
for code in response.entities:
if code.name == name:
return code.id
raise ValueError(f"Wrap-up code '{name}' not found.")
except Exception as e:
print(f"Error fetching wrap-up codes: {e}")
raise e
def set_wrapup_code(
client: PlatformClient,
conversation_id: str,
wrapup_code_id: str,
wrapper_user_id: str
) -> tuple[bool, bool]:
"""
Sets wrap-up code. Returns (success, is_rate_limited).
"""
api = ConversationsApi(client)
request_body = PostConversationWrapupRequest(
wrapup_codes=[{"wrapupCodeId": wrapup_code_id}],
wrapUpBy=wrapper_user_id
)
try:
api.post_conversations_conversation_id_wrapup(
conversation_id=conversation_id,
body=request_body
)
return True, False
except Exception as e:
if hasattr(e, 'status'):
if e.status == 429:
print(f"Rate limited (429) for conversation {conversation_id}")
return False, True
elif e.status == 400:
print(f"Bad Request (400) for conversation {conversation_id}: {e}")
return False, False
elif e.status == 404:
print(f"Conversation {conversation_id} not found.")
return False, False
else:
print(f"Error ({e.status}) for conversation {conversation_id}: {e}")
return False, False
else:
print(f"Unexpected error: {e}")
return False, False
def set_wrapup_with_retry(
client: PlatformClient,
conversation_id: str,
wrapup_code_id: str,
wrapper_user_id: str,
max_retries: int = 3,
base_delay: float = 1.0
) -> bool:
for attempt in range(max_retries):
success, is_rate_limited = set_wrapup_code(
client, conversation_id, wrapup_code_id, wrapper_user_id
)
if success:
return True
if is_rate_limited:
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
print(f"Waiting {delay:.2f}s before retry {attempt + 1}...")
time.sleep(delay)
else:
# Non-retryable error
return False
print(f"Failed to set wrap-up code after {max_retries} retries.")
return False
if __name__ == "__main__":
try:
# 1. Authenticate
client = get_authenticated_client()
# 2. Configuration
TARGET_CONVERSATION_ID = "your-conversation-id-here" # Replace with actual ID
WRAPUP_CODE_NAME = "Call Resolved" # Replace with actual wrap-up code name
MEDIA_TYPE = "voice"
WRAPPER_USER_ID = "your-agent-or-service-user-id" # Replace with actual user ID
# 3. Get Wrap-Up Code ID
print(f"Fetching wrap-up code ID for '{WRAPUP_CODE_NAME}'...")
wrapup_code_id = get_wrapup_code_id(client, WRAPUP_CODE_NAME, MEDIA_TYPE)
print(f"Found wrap-up code ID: {wrapup_code_id}")
# 4. Set Wrap-Up Code
print(f"Setting wrap-up code on conversation {TARGET_CONVERSATION_ID}...")
success = set_wrapup_with_retry(
client,
TARGET_CONVERSATION_ID,
wrapup_code_id,
WRAPPER_USER_ID
)
if success:
print("Wrap-up code set successfully.")
else:
print("Failed to set wrap-up code.")
except Exception as e:
print(f"Fatal error: {e}")
Common Errors & Debugging
Error: 400 Bad Request - “Conversation is not in a closed state”
- Cause: You are attempting to set a wrap-up code on a conversation that is still
active,queued, orwaiting. Wrap-up codes can only be applied after the interaction has ended. - Fix: Ensure your workflow waits for the conversation to reach the
closedorendedstate. You can monitor the conversation state via Webhooks or polling the/conversations/{conversationId}endpoint untilstateisclosed.
Error: 403 Forbidden - “Insufficient permissions”
- Cause: The OAuth token used does not have the
conversation:conversation:writescope. - Fix: Update your OAuth Client in the Genesys Cloud Admin Portal to include the
conversation:conversation:writescope. If using a Service Account, ensure it is assigned a role that grants this permission.
Error: 404 Not Found - “Conversation ID not found”
- Cause: The
conversationIdprovided does not exist or has expired. Genesys Cloud purges conversation data after a retention period. - Fix: Verify the
conversationIdis correct and recent. Check thecreatedDateof the conversation to ensure it is within your data retention window.
Error: 429 Too Many Requests
- Cause: You have exceeded the API rate limit for your organization.
- Fix: Implement exponential backoff as shown in the complete example. Do not retry immediately. Respect the
Retry-Afterheader if present in the response.