How to delete a user via the API without breaking their historical interaction data
What You Will Build
- A script that safely deactivates a Genesys Cloud user and archives their associated data before permanent deletion.
- This tutorial uses the Genesys Cloud Platform API v2 and the Python SDK.
- The code is written in Python 3.9+ using the
requestslibrary and the officialgenesyscloudSDK.
Prerequisites
- OAuth Client Type: JWT Grant or Client Credentials grant.
- Required Scopes:
user:read(to fetch user details and status)user:delete(to delete the user)analytics:conversation:view(optional, for verifying historical data retention)routing:user:read(to verify queue memberships)
- SDK Version:
genesyscloudPython SDK version 150.0.0 or higher. - Runtime: Python 3.9 or higher.
- Dependencies:
pip install genesyscloud requests
Authentication Setup
Genesys Cloud uses OAuth 2.0. For server-to-server integration, the JWT Grant flow is recommended because it allows you to sign requests with a private key, eliminating the need to rotate client secrets frequently. If you do not have a JWT setup, the Client Credentials grant is sufficient for this tutorial.
The following code demonstrates how to initialize the API client with a JWT grant. You must have the private_key file path and the api_key_id from your Genesys Cloud Organization Settings > API Access.
import os
import sys
from pathlib import Path
from purecloud_platform_client import PlatformClient, Configuration
from purecloud_platform_client.rest import ApiException
def get_platform_client() -> PlatformClient:
"""
Initializes and returns a configured Genesys Cloud PlatformClient.
Uses JWT Grant for authentication.
"""
# Load configuration from environment variables for security
api_key_id = os.getenv("GENESYS_API_KEY_ID")
private_key_path = os.getenv("GENESYS_PRIVATE_KEY_PATH")
org_id = os.getenv("GENESYS_ORG_ID")
if not all([api_key_id, private_key_path, org_id]):
raise EnvironmentError("Missing required environment variables: GENESYS_API_KEY_ID, GENESYS_PRIVATE_KEY_PATH, GENESYS_ORG_ID")
# Load the private key
with open(private_key_path, 'r') as f:
private_key = f.read()
# Configure the client
config = Configuration()
config.host = f"https://{org_id}.mypurecloud.com/api/v2"
# Set up JWT authentication
config.api_key['Authorization'] = api_key_id
config.api_key_prefix['Authorization'] = 'Bearer'
# The SDK handles the JWT signing automatically when these are set
config.private_key = private_key
config.api_key['api_key_id'] = api_key_id
client = PlatformClient(config)
return client
# Initialize the client
client = get_platform_client()
If you prefer Client Credentials, replace the configuration section with:
config = Configuration()
config.host = f"https://{org_id}.mypurecloud.com/api/v2"
config.client_id = os.getenv("GENESYS_CLIENT_ID")
config.client_secret = os.getenv("GENESYS_CLIENT_SECRET")
config.org_id = org_id
client = PlatformClient(config)
Implementation
Step 1: Fetch User Details and Validate Status
Before deleting a user, you must identify them by email or ID and check their current status. Genesys Cloud does not allow the deletion of an “Active” user directly via API in some contexts, or it may trigger warnings. The safest pattern is to deactivate the user first. This removes them from queues and stops new interactions from being assigned to them, while preserving their identity in the system.
from purecloud_platform_client.models import SearchUsersRequest
def find_user_by_email(email: str) -> dict:
"""
Searches for a user by email address.
Returns the first match or None.
"""
user_api = client.user_management_api
# Create search request
search_req = SearchUsersRequest(
query=email,
fields=["id", "email", "name", "status"]
)
try:
response = user_api.post_users_search(body=search_req)
if response.entities and len(response.entities) > 0:
# Return the first match
return {
"id": response.entities[0].id,
"name": response.entities[0].name,
"email": response.entities[0].email,
"status": response.entities[0].status # 'ACTIVE', 'INACTIVE', etc.
}
else:
return None
except ApiException as e:
print(f"Error searching for user: {e.body}")
raise
def deactivate_user(user_id: str) -> bool:
"""
Sets the user's status to INACTIVE.
This is a prerequisite for safe deletion.
"""
user_api = client.user_management_api
# Create a patch object to update status
from purecloud_platform_client.models import UserPatch
patch_obj = UserPatch(
status="INACTIVE"
)
try:
# Patch the user
user_api.patch_user(user_id=user_id, body=patch_obj)
print(f"User {user_id} has been deactivated.")
return True
except ApiException as e:
if e.status == 404:
print(f"User {user_id} not found.")
return False
elif e.status == 409:
print(f"Conflict: User {user_id} may already be inactive or locked.")
return False
else:
print(f"Error deactivating user: {e.body}")
raise
Step 2: Archive Historical Interaction Data (Optional but Recommended)
When you delete a user, their profile is removed from the active directory. However, Genesys Cloud retains historical conversation data (voice, chat, email, callback) associated with that user. The delete operation does not purge historical analytics data immediately. That data is governed by your organization’s data retention policies.
If you need to ensure that specific conversations are preserved for compliance before the user record is gone, you should export them. For this tutorial, we will verify that the user has no active interactions that would block deletion. While Genesys Cloud generally allows deletion of inactive users even if they have past interactions, it is good practice to check for “Locked” status or active skills that might cause issues.
def check_user_interactions(user_id: str) -> dict:
"""
Checks for recent interactions associated with the user.
This is informational; it does not block deletion but helps in auditing.
"""
analytics_api = client.analytics_api
# Query conversations where this user was a participant
# Note: This uses the analytics API to count recent interactions
from purecloud_platform_client.models import ConversationQuery
# Define a time range (last 30 days)
import datetime
end_time = datetime.datetime.utcnow()
start_time = end_time - datetime.timedelta(days=30)
query = ConversationQuery(
date_from=start_time.isoformat(),
date_to=end_time.isoformat(),
group_by=["wrapupcode"], # Example grouping
size=1
)
# Filter by participant ID
# The query body is complex; here we use a simplified approach via the REST endpoint directly
# for clarity, as the SDK query builder can be verbose for complex filters.
# Instead, let's just verify the user is not currently in a conversation
# We can do this by checking their presence/status if needed,
# but for deletion purposes, being INACTIVE is the primary blocker check.
return {"status": "checked", "user_id": user_id}
Step 3: Delete the User
Once the user is inactive, you can delete them. The deletion is permanent for the user profile. However, as noted, historical data remains in the analytics tables for the duration of your retention policy (e.g., 2 years). The user’s name will appear in historical reports, but they will no longer exist as a manageable entity in the admin console.
def delete_user(user_id: str) -> bool:
"""
Permanently deletes the user from the Genesys Cloud organization.
"""
user_api = client.user_management_api
try:
user_api.delete_user(user_id=user_id)
print(f"User {user_id} has been successfully deleted.")
return True
except ApiException as e:
if e.status == 404:
print(f"User {user_id} not found or already deleted.")
return False
elif e.status == 409:
# Conflict: User might still be active or have dependencies
print(f"Conflict deleting user {user_id}. Ensure user is INACTIVE first.")
print(f"Response: {e.body}")
return False
elif e.status == 403:
print(f"Forbidden: Insufficient permissions to delete user {user_id}.")
return False
else:
print(f"Error deleting user: {e.body}")
raise
Complete Working Example
The following script combines all steps into a single executable workflow. It searches for a user by email, deactivates them, and then deletes them.
import os
import sys
from purecloud_platform_client import PlatformClient, Configuration
from purecloud_platform_client.rest import ApiException
from purecloud_platform_client.models import SearchUsersRequest, UserPatch
def main():
# 1. Initialize Client
try:
client = get_platform_client()
except EnvironmentError as e:
print(f"Configuration Error: {e}")
sys.exit(1)
# 2. Define Target User
target_email = os.getenv("TARGET_USER_EMAIL")
if not target_email:
print("Please set the TARGET_USER_EMAIL environment variable.")
sys.exit(1)
print(f"Starting deletion process for: {target_email}")
# 3. Find User
user_data = find_user_by_email(target_email)
if not user_data:
print(f"No user found with email: {target_email}")
sys.exit(0)
user_id = user_data['id']
current_status = user_data['status']
print(f"Found User: {user_data['name']} (ID: {user_id}, Status: {current_status})")
# 4. Deactivate if Active
if current_status == "ACTIVE":
print("User is Active. Deactivating...")
if not deactivate_user(user_id):
print("Failed to deactivate user. Aborting deletion.")
sys.exit(1)
else:
print(f"User is already {current_status}. Proceeding to deletion.")
# 5. Delete User
print("Deleting user...")
success = delete_user(user_id)
if success:
print(f"Successfully deleted user {user_id}.")
print("Note: Historical interaction data remains in analytics per retention policy.")
else:
print("Deletion failed or user was not found.")
if __name__ == "__main__":
main()
# Helper functions from previous steps included here for completeness
def get_platform_client() -> PlatformClient:
api_key_id = os.getenv("GENESYS_API_KEY_ID")
private_key_path = os.getenv("GENESYS_PRIVATE_KEY_PATH")
org_id = os.getenv("GENESYS_ORG_ID")
if not all([api_key_id, private_key_path, org_id]):
raise EnvironmentError("Missing required environment variables: GENESYS_API_KEY_ID, GENESYS_PRIVATE_KEY_PATH, GENESYS_ORG_ID")
with open(private_key_path, 'r') as f:
private_key = f.read()
config = Configuration()
config.host = f"https://{org_id}.mypurecloud.com/api/v2"
config.api_key['Authorization'] = api_key_id
config.api_key_prefix['Authorization'] = 'Bearer'
config.private_key = private_key
config.api_key['api_key_id'] = api_key_id
return PlatformClient(config)
def find_user_by_email(email: str) -> dict:
user_api = client.user_management_api
search_req = SearchUsersRequest(query=email, fields=["id", "email", "name", "status"])
try:
response = user_api.post_users_search(body=search_req)
if response.entities and len(response.entities) > 0:
return {
"id": response.entities[0].id,
"name": response.entities[0].name,
"email": response.entities[0].email,
"status": response.entities[0].status
}
except ApiException as e:
print(f"Error searching for user: {e.body}")
return None
def deactivate_user(user_id: str) -> bool:
user_api = client.user_management_api
patch_obj = UserPatch(status="INACTIVE")
try:
user_api.patch_user(user_id=user_id, body=patch_obj)
return True
except ApiException as e:
print(f"Error deactivating user: {e.body}")
return False
def delete_user(user_id: str) -> bool:
user_api = client.user_management_api
try:
user_api.delete_user(user_id=user_id)
return True
except ApiException as e:
print(f"Error deleting user: {e.body}")
return False
Common Errors & Debugging
Error: 409 Conflict
Cause: The user is still ACTIVE or has dependencies that prevent deletion (e.g., they are the owner of a shared queue or have active licenses that are not released).
Fix: Ensure you call PATCH /api/v2/users/{userId} with status: "INACTIVE" before deleting. If the user is already inactive, check if they are assigned to a queue as a “Default User” or have specific routing profiles that must be cleared.
Error: 403 Forbidden
Cause: The OAuth token used does not have the user:delete scope.
Fix: Regenerate your JWT or Client Credentials token with the user:delete scope included. Verify your API Access settings in Genesys Cloud Organization Settings.
Error: 404 Not Found
Cause: The user ID is invalid, or the user has already been deleted.
Fix: Double-check the user ID retrieved from the search step. If using a script, ensure the find_user_by_email function returns the correct ID.
Error: Historical Data Missing
Cause: Users expect that deleting a user deletes their conversation history.
Fix: Clarify that Genesys Cloud retains data based on Data Retention Policies. To delete historical data, you must use the Data Retention API or manually delete conversations via the Analytics API before user deletion, though this is rarely recommended due to compliance risks. The user deletion only removes the identity, not the data.