How to delete a user via the API without breaking their historical interaction data
What You Will Build
- A script that safely deactivates and removes a user from Genesys Cloud CX while preserving the integrity of their historical conversation records.
- This tutorial uses the Genesys Cloud CX REST API and Python SDK.
- The implementation is written in Python 3.9+ using the
genesyscloudSDK andrequestsfor fallback operations.
Prerequisites
- OAuth Client Type: Service Account or JWT (JSON Web Token) authentication is required. Client Credentials flow is also acceptable if the client has the necessary scopes.
- Required Scopes:
user:read(to verify user existence and status)user:write(to deactivate the user)user:delete(to permanently remove the user, if applicable)conversation:read(to verify historical data integrity, optional but recommended)
- SDK Version: Genesys Cloud Python SDK
v2(latest stable). - Language/Runtime: Python 3.9 or higher.
- External Dependencies:
genesyscloudrequestspython-dotenv(for secure credential management)
Authentication Setup
Genesys Cloud CX uses OAuth 2.0. For API-driven user management, a Service Account or JWT is preferred because it does not require interactive login and can run in background processes.
First, install the required packages:
pip install genesyscloud requests python-dotenv
Create a .env file to store your credentials securely:
GENESYS_CLOUD_REGION=us-east-1
GENESYS_CLOUD_CLIENT_ID=your_client_id
GENESYS_CLOUD_CLIENT_SECRET=your_client_secret
GENESYS_CLOUD_JWT_PRIVATE_KEY=-----BEGIN RSA PRIVATE KEY-----...
Here is the authentication logic using the Genesys Cloud Python SDK. The SDK handles token caching and refresh automatically.
import os
from dotenv import load_dotenv
from genesyscloud.rest import Configuration
from genesyscloud.api_client import ApiClient
from genesyscloud.users_api import UsersApi
from genesyscloud.conversations_api import ConversationsApi
from genesyscloud.analytics_api import AnalyticsApi
import time
load_dotenv()
def get_api_client():
"""
Initializes and returns the Genesys Cloud API client.
Uses JWT authentication by default. Falls back to Client Credentials if JWT key is not present.
"""
region = os.getenv("GENESYS_CLOUD_REGION")
client_id = os.getenv("GENESYS_CLOUD_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLOUD_CLIENT_SECRET")
private_key = os.getenv("GENESYS_CLOUD_JWT_PRIVATE_KEY")
config = Configuration(
host=f"https://{region}.mypurecloud.com",
client_id=client_id,
client_secret=client_secret,
jwt_private_key=private_key
)
api_client = ApiClient(configuration=config)
return api_client
# Initialize APIs
api_client = get_api_client()
users_api = UsersApi(api_client)
conversations_api = ConversationsApi(api_client)
Implementation
Step 1: Verify User Existence and Status
Before attempting to delete or deactivate a user, you must verify that the user exists and check their current status. Deleting an already deleted user will return a 404. Activating a deactivated user is different from deleting one.
The critical distinction in Genesys Cloud is between Deactivating and Deleting.
- Deactivating (
PUT /api/v2/users/{id}withstatus: "inactive"): The user remains in the system. Historical data (chats, emails, tasks) retains the link to this user ID. This is the safe way to “remove” a user without breaking history. - Deleting (
DELETE /api/v2/users/{id}): The user is permanently removed. Genesys Cloud does not delete historical interactions associated with the user. Thetoorfromfields in historical data will still reference the user ID, but the user profile will return 404. This can break UI components that try to resolve the user name from the ID.
Best Practice: Always deactivate first. Only delete if you are certain no internal tools rely on resolving the user profile for historical reports.
def get_user_by_email(email: str):
"""
Retrieves a user object by their email address.
"""
try:
# Search for users by email
response = users_api.post_users_search(
body={
"query": email,
"searchCriteria": ["email"]
}
)
if response.entities and len(response.entities) > 0:
return response.entities[0]
else:
return None
except Exception as e:
print(f"Error searching for user: {e}")
return None
def check_user_status(user_id: str):
"""
Gets the detailed user object to check status and active interactions.
"""
try:
user = users_api.get_user_by_id(user_id=user_id)
return user
except Exception as e:
if e.status == 404:
print(f"User {user_id} not found.")
else:
print(f"Error fetching user details: {e}")
return None
Step 2: Check for Active Interactions
You cannot deactivate a user who has active conversations. The API will return a 409 Conflict if you attempt to deactivate a user with ongoing chats, calls, or tasks. You must check for active interactions first.
def has_active_conversations(user_id: str):
"""
Checks if the user has any active conversations.
Returns True if active conversations exist, False otherwise.
"""
try:
# Query for active conversations involving this user
# Note: The analytics API is the most reliable way to find active sessions
# However, a simpler check is often sufficient via the users API which
# sometimes includes active session counts, but let's use the conversations API.
# We construct a query for active conversations where this user is a participant
body = {
"view": "realtime",
"dateFrom": "2023-01-01T00:00:00.000Z", # Arbitrary past date
"dateTo": "2099-01-01T00:00:00.000Z", # Arbitrary future date
"query": {
"predicates": [
{
"type": "equals",
"field": "participants.userId",
"value": user_id
},
{
"type": "equals",
"field": "state",
"value": "active"
}
]
},
"size": 1
}
response = conversations_api.post_conversations_details_query(
body=body
)
if response.entities and len(response.entities) > 0:
return True
return False
except Exception as e:
print(f"Error checking active conversations: {e}")
return False # Assume false if error, but handle gracefully
Step 3: Deactivate the User (Safe Removal)
Deactivating the user is the standard procedure for “removing” a user while keeping their historical data intact. The user ID remains valid in historical records.
def deactivate_user(user_id: str):
"""
Deactivates the user by setting their status to 'inactive'.
This preserves historical data links.
"""
try:
# Update user status to inactive
users_api.update_user(
user_id=user_id,
body={
"status": "inactive"
}
)
print(f"Successfully deactivated user {user_id}")
return True
except Exception as e:
if e.status == 409:
print(f"Cannot deactivate user {user_id}: They have active conversations.")
elif e.status == 400:
print(f"Bad request: {e.body}")
else:
print(f"Error deactivating user: {e}")
return False
Step 4: Permanently Delete the User (Optional)
If you must permanently remove the user (e.g., GDPR “Right to be Forgotten” where you also want to remove the identity from the directory, though data retention policies may still keep the interaction logs), use the DELETE endpoint.
Warning: This breaks UI lookups. If a dashboard tries to display “Handled By: John Doe” for a chat from 2023, it will fail to resolve “John Doe” and may show “Unknown” or the raw UUID.
def delete_user(user_id: str):
"""
Permanently deletes the user.
Use with caution. Historical interactions remain but the user profile is gone.
"""
try:
users_api.delete_user(user_id=user_id)
print(f"Successfully deleted user {user_id}")
return True
except Exception as e:
if e.status == 404:
print(f"User {user_id} already deleted.")
else:
print(f"Error deleting user: {e}")
return False
Complete Working Example
This script combines all steps into a single workflow. It searches for a user by email, checks for active conversations, deactivates them, and optionally deletes them.
import os
import sys
from dotenv import load_dotenv
from genesyscloud.rest import Configuration
from genesyscloud.api_client import ApiClient
from genesyscloud.users_api import UsersApi
from genesyscloud.conversations_api import ConversationsApi
load_dotenv()
def initialize_api():
region = os.getenv("GENESYS_CLOUD_REGION")
client_id = os.getenv("GENESYS_CLOUD_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLOUD_CLIENT_SECRET")
private_key = os.getenv("GENESYS_CLOUD_JWT_PRIVATE_KEY")
if not all([region, client_id, client_secret]):
raise ValueError("Missing required environment variables.")
config = Configuration(
host=f"https://{region}.mypurecloud.com",
client_id=client_id,
client_secret=client_secret,
jwt_private_key=private_key
)
api_client = ApiClient(configuration=config)
return UsersApi(api_client), ConversationsApi(api_client)
def find_user_by_email(users_api, email):
try:
response = users_api.post_users_search(
body={
"query": email,
"searchCriteria": ["email"]
}
)
if response.entities:
return response.entities[0]
except Exception as e:
print(f"Search failed: {e}")
return None
def check_active_conversations(conversations_api, user_id):
try:
body = {
"view": "realtime",
"dateFrom": "2023-01-01T00:00:00.000Z",
"dateTo": "2099-01-01T00:00:00.000Z",
"query": {
"predicates": [
{"type": "equals", "field": "participants.userId", "value": user_id},
{"type": "equals", "field": "state", "value": "active"}
]
},
"size": 1
}
response = conversations_api.post_conversations_details_query(body=body)
return bool(response.entities)
except Exception as e:
print(f"Error checking conversations: {e}")
return False
def deactivate_and_delete_user(email, delete_permanently=False):
users_api, conversations_api = initialize_api()
# Step 1: Find User
user = find_user_by_email(users_api, email)
if not user:
print(f"User with email {email} not found.")
return
user_id = user.id
user_name = user.name
print(f"Found user: {user_name} (ID: {user_id})")
print(f"Current status: {user.status}")
# Step 2: Check Active Conversations
if check_active_conversations(conversations_api, user_id):
print(f"User {user_name} has active conversations. Cannot deactivate.")
return
# Step 3: Deactivate
if user.status != "inactive":
try:
users_api.update_user(user_id=user_id, body={"status": "inactive"})
print(f"User {user_name} deactivated.")
except Exception as e:
print(f"Failed to deactivate user: {e}")
return
else:
print(f"User {user_name} is already inactive.")
# Step 4: Delete (Optional)
if delete_permanently:
try:
users_api.delete_user(user_id=user_id)
print(f"User {user_name} permanently deleted.")
except Exception as e:
print(f"Failed to delete user: {e}")
else:
print(f"User {user_name} remains in system as inactive. History preserved.")
if __name__ == "__main__":
target_email = os.getenv("TARGET_USER_EMAIL", "[email protected]")
delete_flag = os.getenv("DELETE_PERMANENTLY", "false").lower() == "true"
deactivate_and_delete_user(target_email, delete_flag)
Common Errors & Debugging
Error: 409 Conflict - User has active conversations
What causes it:
The user is currently engaged in a chat, call, or has an assigned task that is in an “active” state. Genesys Cloud prevents deactivation to ensure conversation continuity.
How to fix it:
- Wait for the conversation to end.
- Transfer the active conversation to another user.
- If this is an automated process, implement a retry loop with exponential backoff.
Code showing the fix:
import time
def deactivate_with_retry(users_api, user_id, max_retries=3, delay=60):
for attempt in range(max_retries):
try:
users_api.update_user(user_id=user_id, body={"status": "inactive"})
return True
except Exception as e:
if e.status == 409:
print(f"Attempt {attempt + 1}: User has active conversations. Waiting {delay}s...")
time.sleep(delay)
else:
raise e
print("Max retries reached. User still has active conversations.")
return False
Error: 404 Not Found - User does not exist
What causes it:
The user ID or email provided does not match any user in the organization. This can happen if the user was already deleted.
How to fix it:
Verify the email address. Use the post_users_search endpoint to find the correct ID before attempting deletion.
Error: 403 Forbidden - Insufficient Permissions
What causes it:
The OAuth token used does not have the user:write or user:delete scope.
How to fix it:
- Go to the Genesys Cloud Admin Console.
- Navigate to Admin > Security > OAuth Clients.
- Edit your client.
- Ensure User > User > Write and Delete scopes are checked.
- Regenerate the token.
Error: 429 Too Many Requests
What causes it:
You are making too many API calls in a short period. Genesys Cloud enforces rate limits per client ID.
How to fix it:
Implement rate limiting in your code. Respect the Retry-After header in the response.
Code showing the fix:
import time
def safe_api_call(func, *args, **kwargs):
max_retries = 5
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
if e.status == 429:
retry_after = int(e.headers.get('Retry-After', 2 ** attempt))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
else:
raise e
raise Exception("Max retries exceeded for rate limiting.")