How to Set Wrap-Up Codes Programmatically After an Interaction Ends

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 genesyscloud SDK and raw httpx for HTTP requests.

Prerequisites

  • OAuth Client: A Genesys Cloud OAuth client with the following scopes:
    • conversation:wrapup:write
    • conversation:view
  • SDK Version: genesyscloud Python SDK v12.0.0 or later.
  • Runtime: Python 3.9+.
  • Dependencies:
    • genesyscloud
    • httpx (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.")

Official References