How to Set Wrap-Up Codes Programmatically After an Interaction Ends
What You Will Build
- This tutorial demonstrates how to programmatically assign wrap-up codes to a completed Genesys Cloud conversation using the API.
- The solution utilizes the Genesys Cloud Conversations API endpoint
POST /api/v2/conversations/{conversationId}/wrapup. - The implementation is provided in Python using the official
genesyscloudSDK and rawhttpxfor HTTP requests.
Prerequisites
- OAuth Client: A Genesys Cloud OAuth client with the following scopes:
conversation:wrapup:writeconversation:view
- SDK Version:
genesyscloudPython SDK v12.0.0 or later. - Runtime: Python 3.9+.
- Dependencies:
genesyscloudhttpx(for raw API examples)pydantic(for data validation)
Authentication Setup
Genesys Cloud uses OAuth 2.0 for authentication. For server-to-server integrations, use the Client Credentials flow. The following code initializes the SDK with a private key file, which is the recommended approach for production environments to avoid exposing secrets in environment variables.
import os
from genesyscloud.auth import oauth_client
def get_platform_client():
"""
Initializes and returns the Genesys Cloud Platform Client.
"""
# Define your environment (e.g., 'us-east-1', 'eu-west-1')
environment = os.getenv("GENESYS_ENVIRONMENT", "us-east-1")
# Path to your private key file
private_key_path = os.getenv("GENESYS_PRIVATE_KEY_PATH")
client_id = os.getenv("GENESYS_CLIENT_ID")
if not private_key_path or not client_id:
raise ValueError("GENESYS_PRIVATE_KEY_PATH and GENESYS_CLIENT_ID must be set.")
# Initialize the OAuth client
oauth_client.init_private_key(private_key_path, client_id, environment)
# Get the platform client instance
return oauth_client.get_platform_client()
Implementation
Step 1: Retrieve Available Wrap-Up Codes
Before assigning a wrap-up code, you must identify the valid code identifiers available to the agent or the specific interaction type. Wrap-up codes are defined in the Wrap-Up Code Configuration. You can retrieve the list of available codes for a specific user or globally.
The endpoint GET /api/v2/wrappingcodes returns all wrap-up codes. However, it is often more efficient to filter by the id if you already know it, or retrieve the code by its name if the name is unique.
from genesyscloud.wrapping_codes.api import wrapping_codes_api
def get_wrapup_code_by_name(platform_client, code_name: str) -> str:
"""
Retrieves the ID of a wrap-up code by its name.
Args:
platform_client: The initialized Genesys Cloud platform client.
code_name: The exact name of the wrap-up code.
Returns:
The ID of the wrap-up code.
Raises:
ValueError: If the code is not found.
"""
api_instance = wrapping_codes_api.WrappingCodesApi(platform_client)
try:
# Retrieve all wrap-up codes
# Note: For large configurations, consider filtering by 'division' if applicable
response = api_instance.get_wrapping_codes()
# Iterate through results to find the matching name
for code in response.entities:
if code.name == code_name:
return code.id
raise ValueError(f"Wrap-up code '{code_name}' not found.")
except Exception as e:
print(f"Error retrieving wrap-up codes: {e}")
raise
Step 2: Construct the Wrap-Up Request Body
The POST /api/v2/conversations/{conversationId}/wrapup endpoint requires a JSON body containing the code (ID of the wrap-up code) and optionally a duration (in seconds) and text (optional note).
The duration field is critical for analytics. It represents the time spent in the wrap-up state. If not provided, Genesys Cloud may default to zero or use the actual elapsed time depending on configuration, but explicit values are recommended for accurate reporting.
from dataclasses import dataclass
from typing import Optional
@dataclass
class WrapUpRequest:
"""
Data structure for the wrap-up API request body.
"""
code: str # The ID of the wrap-up code
duration: int = 0 # Duration in seconds
text: Optional[str] = None # Optional note
def to_dict(self) -> dict:
"""
Converts the dataclass to a dictionary for JSON serialization.
"""
payload = {
"code": self.code,
"duration": self.duration
}
if self.text:
payload["text"] = self.text
return payload
Step 3: Execute the Wrap-Up Assignment
This is the core operation. You must provide the conversationId of the ended interaction. The conversation must be in the ENDED or WRAPUP state. If the conversation is still ACTIVE, this call will fail with a 400 Bad Request.
The following function uses the SDK to perform the assignment. It includes error handling for common scenarios such as invalid conversation IDs or missing permissions.
from genesyscloud.conversations.api import conversations_api
import httpx
def assign_wrapup_code(platform_client, conversation_id: str, wrapup_request: WrapUpRequest) -> dict:
"""
Assigns a wrap-up code to a completed conversation.
Args:
platform_client: The initialized Genesys Cloud platform client.
conversation_id: The ID of the conversation.
wrapup_request: The WrapUpRequest object containing code, duration, and text.
Returns:
The response body from the API.
Raises:
Exception: If the API call fails.
"""
api_instance = conversations_api.ConversationsApi(platform_client)
payload = wrapup_request.to_dict()
try:
# The SDK method corresponds to POST /api/v2/conversations/{conversationId}/wrapup
response = api_instance.post_conversations_conversation_id_wrapup(
conversation_id=conversation_id,
body=payload
)
return response.to_dict() if hasattr(response, 'to_dict') else response
except Exception as e:
# Handle specific HTTP errors
if hasattr(e, 'status') and e.status == 400:
raise ValueError(f"Invalid request for conversation {conversation_id}. Ensure the conversation is ended. Details: {e.body}")
elif hasattr(e, 'status') and e.status == 401:
raise PermissionError("Authentication failed. Check OAuth token.")
elif hasattr(e, 'status') and e.status == 403:
raise PermissionError("Insufficient permissions. Ensure 'conversation:wrapup:write' scope is present.")
else:
raise e
Step 4: Verify the Assignment (Optional)
To confirm the wrap-up code was applied, you can retrieve the conversation details. The wrapup object will be present in the conversation entity if successfully assigned.
def verify_wrapup_assignment(platform_client, conversation_id: str) -> dict:
"""
Retrieves the conversation details to verify the wrap-up code assignment.
Args:
platform_client: The initialized Genesys Cloud platform client.
conversation_id: The ID of the conversation.
Returns:
The conversation details including the wrap-up object.
"""
api_instance = conversations_api.ConversationsApi(platform_client)
try:
response = api_instance.get_conversations_conversation_id(
conversation_id=conversation_id
)
# Check if wrap-up is present
if response.wrapup:
return {
"code_id": response.wrapup.code.id,
"code_name": response.wrapup.code.name,
"duration": response.wrapup.duration,
"text": response.wrapup.text
}
else:
return {"status": "no_wrapup_assigned"}
except Exception as e:
print(f"Error verifying wrap-up: {e}")
raise
Complete Working Example
The following script combines all steps into a single executable module. It authenticates, finds a wrap-up code by name, assigns it to a specific conversation, and verifies the result.
import os
import sys
from genesyscloud.auth import oauth_client
from genesyscloud.wrapping_codes.api import wrapping_codes_api
from genesyscloud.conversations.api import conversations_api
from dataclasses import dataclass
from typing import Optional
@dataclass
class WrapUpRequest:
code: str
duration: int = 0
text: Optional[str] = None
def to_dict(self) -> dict:
payload = {"code": self.code, "duration": self.duration}
if self.text:
payload["text"] = self.text
return payload
def main():
# Configuration
ENVIRONMENT = os.getenv("GENESYS_ENVIRONMENT", "us-east-1")
PRIVATE_KEY_PATH = os.getenv("GENESYS_PRIVATE_KEY_PATH")
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CONVERSATION_ID = os.getenv("GENESYS_CONVERSATION_ID")
WRAPUP_CODE_NAME = os.getenv("GENESYS_WRAPUP_CODE_NAME", "General")
WRAPUP_DURATION = int(os.getenv("GENESYS_WRAPUP_DURATION", "10"))
WRAPUP_TEXT = os.getenv("GENESYS_WRAPUP_TEXT", "Programmatic assignment")
if not all([PRIVATE_KEY_PATH, CLIENT_ID, CONVERSATION_ID]):
print("Error: Missing required environment variables.")
sys.exit(1)
try:
# 1. Initialize Platform Client
oauth_client.init_private_key(PRIVATE_KEY_PATH, CLIENT_ID, ENVIRONMENT)
platform_client = oauth_client.get_platform_client()
# 2. Get Wrap-Up Code ID
wrapping_codes_api_instance = wrapping_codes_api.WrappingCodesApi(platform_client)
response_codes = wrapping_codes_api_instance.get_wrapping_codes()
target_code_id = None
for code in response_codes.entities:
if code.name == WRAPUP_CODE_NAME:
target_code_id = code.id
break
if not target_code_id:
print(f"Error: Wrap-up code '{WRAPUP_CODE_NAME}' not found.")
sys.exit(1)
print(f"Found Wrap-Up Code ID: {target_code_id}")
# 3. Prepare Wrap-Up Request
wrapup_req = WrapUpRequest(
code=target_code_id,
duration=WRAPUP_DURATION,
text=WRAPUP_TEXT
)
# 4. Assign Wrap-Up Code
conversations_api_instance = conversations_api.ConversationsApi(platform_client)
try:
api_response = conversations_api_instance.post_conversations_conversation_id_wrapup(
conversation_id=CONVERSATION_ID,
body=wrapup_req.to_dict()
)
print(f"Success: Wrap-up code assigned to conversation {CONVERSATION_ID}")
print(f"Response: {api_response}")
except Exception as e:
if hasattr(e, 'status'):
print(f"API Error {e.status}: {e.body}")
else:
print(f"Unexpected error: {e}")
sys.exit(1)
# 5. Verify Assignment
verification_response = conversations_api_instance.get_conversations_conversation_id(
conversation_id=CONVERSATION_ID
)
if verification_response.wrapup:
print(f"Verification Successful:")
print(f" Code Name: {verification_response.wrapup.code.name}")
print(f" Duration: {verification_response.wrapup.duration}s")
print(f" Text: {verification_response.wrapup.text}")
else:
print("Warning: Wrap-up code not found in conversation details immediately after assignment.")
except Exception as e:
print(f"Fatal Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 400 Bad Request - “Conversation is not in a valid state”
Cause: The API only accepts wrap-up assignments for conversations that are ENDED or WRAPUP. If the conversation is ACTIVE, RINGING, or QUEUED, the call will fail.
Fix: Ensure the interaction has fully completed. You can check the conversation state via GET /api/v2/conversations/{conversationId} before attempting the assignment.
# Check conversation state before assignment
def is_conversation_ready_for_wrapup(platform_client, conversation_id: str) -> bool:
api_instance = conversations_api.ConversationsApi(platform_client)
response = api_instance.get_conversations_conversation_id(conversation_id=conversation_id)
valid_states = ["ENDED", "WRAPUP"]
return response.state in valid_states
Error: 403 Forbidden - “Insufficient Permissions”
Cause: The OAuth token lacks the conversation:wrapup:write scope.
Fix: Regenerate the OAuth client credentials and ensure the scope conversation:wrapup:write is included in the client configuration.
Error: 404 Not Found - “Wrap-up Code Not Found”
Cause: The code ID provided in the request body does not exist, or the code is not active.
Fix: Verify the wrap-up code ID using GET /api/v2/wrappingcodes and ensure the code is not deleted or disabled.
Error: 429 Too Many Requests
Cause: Rate limiting has been exceeded. Genesys Cloud APIs enforce rate limits per client ID.
Fix: Implement exponential backoff retry logic.
import time
import httpx
def post_with_retry(api_call_func, max_retries: int = 3, backoff_factor: int = 1):
"""
Generic retry wrapper for API calls with exponential backoff.
"""
for attempt in range(max_retries):
try:
return api_call_func()
except Exception as e:
if hasattr(e, 'status') and e.status == 429:
wait_time = backoff_factor ** (attempt + 1)
print(f"Rate limited. Retrying in {wait_time} seconds...")
time.sleep(wait_time)
else:
raise e
raise Exception("Max retries exceeded for 429 error.")