Bulk-Create Genesys Cloud Users from CSV Using the Python SDK
What You Will Build
- A Python script that reads user data from a CSV file and creates corresponding agents in Genesys Cloud CX.
- This solution uses the
genesyscloudPython SDK to handle authentication, validation, and API calls. - The tutorial covers Python 3.8+ with standard library modules and the official SDK.
Prerequisites
- OAuth Client: A Genesys Cloud OAuth client with
resource:users:writeandresource:users:readscopes. - SDK Version:
genesyscloud>=2.100.0(verify viapip show genesyscloud). - Runtime: Python 3.8 or higher.
- Dependencies:
pip install genesyscloud csv pathlib - CSV Structure: A file named
users.csvwith columns:external_id,name,email,phone_number,division_id.
Authentication Setup
The Genesys Cloud Python SDK handles OAuth token management automatically when initialized with a client ID, client secret, and region. You must configure these values in your environment or directly in the script.
import os
from genesyscloud.platform_client_v2 import PlatformClientBuilder
def get_platform_client() -> PlatformClientBuilder:
"""
Initializes and returns the configured PlatformClient.
"""
# Load credentials from environment variables
client_id = os.environ.get("GENESYS_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLIENT_SECRET")
region = os.environ.get("GENESYS_REGION", "us-east-1")
if not client_id or not client_secret:
raise EnvironmentError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET must be set.")
# Configure the platform client
builder = PlatformClientBuilder(
client_id=client_id,
client_secret=client_secret,
region=region
)
# Build the client instance
client = builder.build()
# Verify connectivity by fetching the current user info (optional but recommended for debugging)
try:
client.users.get_me()
print("Authentication successful.")
except Exception as e:
print(f"Authentication failed: {e}")
raise
return client
Required Scopes:
resource:users:write: Required to create new users.resource:users:read: Required to validate existing users or fetch division details if needed.
Implementation
Step 1: Parse and Validate CSV Data
Before making any API calls, you must parse the CSV file and structure the data into objects compatible with the SDK. The Genesys Cloud API requires a unique externalId for each user to prevent duplicates and allow for idempotent updates.
import csv
from pathlib import Path
from typing import List, Dict, Any
def parse_users_csv(filepath: str) -> List[Dict[str, Any]]:
"""
Reads a CSV file and returns a list of dictionaries representing user data.
Args:
filepath: Path to the CSV file.
Returns:
List of dictionaries with keys: external_id, name, email, phone_number, division_id
"""
users = []
path = Path(filepath)
if not path.exists():
raise FileNotFoundError(f"CSV file not found: {filepath}")
with open(path, mode='r', encoding='utf-8') as f:
reader = csv.DictReader(f)
# Validate required columns
required_columns = {'external_id', 'name', 'email', 'phone_number', 'division_id'}
if not required_columns.issubset(set(reader.fieldnames or [])):
missing = required_columns - set(reader.fieldnames or [])
raise ValueError(f"CSV missing required columns: {missing}")
for row in reader:
# Basic validation
if not row.get('external_id') or not row.get('email'):
print(f"Skipping invalid row: {row}")
continue
users.append({
'external_id': row['external_id'].strip(),
'name': row['name'].strip(),
'email': row['email'].strip(),
'phone_number': row.get('phone_number', '').strip(),
'division_id': row.get('division_id', '').strip()
})
return users
Step 2: Construct User Creation Payloads
The Genesys Cloud API expects a specific JSON structure for creating users. You must map the CSV data to the UserCreateRequest model provided by the SDK. Key fields include name, email, externalId, and phoneNumbers.
from genesyscloud.models import UserCreateRequest, PhoneNumber
def create_user_payload(user_data: Dict[str, Any]) -> UserCreateRequest:
"""
Converts a dictionary of user data into a UserCreateRequest object.
Args:
user_data: Dictionary containing user details.
Returns:
UserCreateRequest object ready for the API.
"""
# Initialize phone numbers list
phone_numbers = []
if user_data.get('phone_number'):
phone_numbers.append(PhoneNumber(
type="work",
value=user_data['phone_number'],
primary=True
))
# Construct the request object
request = UserCreateRequest(
name=user_data['name'],
email=user_data['email'],
external_id=user_data['external_id'],
phone_numbers=phone_numbers if phone_numbers else None,
division_id=user_data.get('division_id') # Optional: assigns to specific division
)
return request
Critical Parameter Notes:
external_id: This is the most important field for bulk operations. If you attempt to create a user with anexternal_idthat already exists, the API will return a409 Conflict. You must handle this gracefully.division_id: If omitted, the user is created in the default division. Ensure thedivision_idprovided is valid and accessible by the OAuth client.
Step 3: Execute Bulk Creation with Error Handling
The Genesys Cloud API does not have a single endpoint for bulk user creation. You must iterate through the list and call POST /api/v2/users for each user. To avoid rate limiting (429 Too Many Requests), you should implement a delay between requests or use the SDK’s built-in retry logic.
import time
from genesyscloud.api import UsersApi
from genesyscloud.rest import ApiException
def bulk_create_users(client: PlatformClientBuilder, users_data: List[Dict[str, Any]], delay: float = 0.5) -> List[Dict[str, Any]]:
"""
Creates users in Genesys Cloud based on the provided data.
Args:
client: The configured PlatformClient.
users_data: List of user dictionaries from CSV.
delay: Seconds to wait between API calls to avoid rate limiting.
Returns:
List of results containing success/failure status for each user.
"""
users_api = UsersApi(client)
results = []
print(f"Starting bulk creation for {len(users_data)} users...")
for i, user_data in enumerate(users_data):
try:
# Construct the payload
payload = create_user_payload(user_data)
# Make the API call
response = users_api.post_users(body=payload)
results.append({
'status': 'success',
'external_id': user_data['external_id'],
'genesys_id': response.id,
'name': user_data['name']
})
print(f"[{i+1}/{len(users_data)}] Created: {user_data['name']} (ID: {response.id})")
except ApiException as e:
if e.status == 409:
# Conflict: User with this external_id already exists
results.append({
'status': 'exists',
'external_id': user_data['external_id'],
'name': user_data['name'],
'error': 'User already exists'
})
print(f"[{i+1}/{len(users_data)}] Exists: {user_data['name']}")
elif e.status == 429:
# Rate limit exceeded: Wait longer
wait_time = 5
print(f"[{i+1}/{len(users_data)}] Rate limited. Waiting {wait_time}s...")
time.sleep(wait_time)
# Retry this user by re-adding to the front of the queue (simplified for this example)
users_data.insert(0, user_data)
continue
else:
# Other errors (400, 401, 500)
results.append({
'status': 'error',
'external_id': user_data['external_id'],
'name': user_data['name'],
'error': str(e.body)
})
print(f"[{i+1}/{len(users_data)}] Error: {user_data['name']} - {e.status}")
except Exception as e:
results.append({
'status': 'error',
'external_id': user_data['external_id'],
'name': user_data['name'],
'error': str(e)
})
print(f"[{i+1}/{len(users_data)}] Unexpected Error: {user_data['name']} - {e}")
# Respect rate limits
if i < len(users_data) - 1:
time.sleep(delay)
return results
Rate Limiting Strategy:
- The Genesys Cloud API enforces rate limits per OAuth client.
- The default delay of
0.5seconds between requests is a safe starting point for moderate volumes (up to 100 users). - For larger volumes, monitor the
Retry-Afterheader in 429 responses and adjust the delay dynamically.
Step 4: Process and Report Results
After the bulk creation process completes, you should summarize the results to identify which users were created, which already existed, and which failed.
def print_results(results: List[Dict[str, Any]]):
"""
Prints a summary of the bulk creation results.
"""
success_count = sum(1 for r in results if r['status'] == 'success')
exists_count = sum(1 for r in results if r['status'] == 'exists')
error_count = sum(1 for r in results if r['status'] == 'error')
print("\n--- Bulk Creation Summary ---")
print(f"Total Processed: {len(results)}")
print(f"Successfully Created: {success_count}")
print(f"Already Existed: {exists_count}")
print(f"Errors: {error_count}")
if error_count > 0:
print("\n--- Failed Users ---")
for r in results:
if r['status'] == 'error':
print(f"External ID: {r['external_id']} | Name: {r['name']} | Error: {r['error']}")
Complete Working Example
import os
import csv
import time
from pathlib import Path
from typing import List, Dict, Any
# Genesys Cloud SDK Imports
from genesyscloud.platform_client_v2 import PlatformClientBuilder
from genesyscloud.api import UsersApi
from genesyscloud.rest import ApiException
from genesyscloud.models import UserCreateRequest, PhoneNumber
def get_platform_client() -> PlatformClientBuilder:
"""Initializes and returns the configured PlatformClient."""
client_id = os.environ.get("GENESYS_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLIENT_SECRET")
region = os.environ.get("GENESYS_REGION", "us-east-1")
if not client_id or not client_secret:
raise EnvironmentError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET must be set.")
builder = PlatformClientBuilder(
client_id=client_id,
client_secret=client_secret,
region=region
)
client = builder.build()
try:
client.users.get_me()
print("Authentication successful.")
except Exception as e:
print(f"Authentication failed: {e}")
raise
return client
def parse_users_csv(filepath: str) -> List[Dict[str, Any]]:
"""Reads a CSV file and returns a list of dictionaries representing user data."""
users = []
path = Path(filepath)
if not path.exists():
raise FileNotFoundError(f"CSV file not found: {filepath}")
with open(path, mode='r', encoding='utf-8') as f:
reader = csv.DictReader(f)
required_columns = {'external_id', 'name', 'email', 'phone_number', 'division_id'}
if not required_columns.issubset(set(reader.fieldnames or [])):
missing = required_columns - set(reader.fieldnames or [])
raise ValueError(f"CSV missing required columns: {missing}")
for row in reader:
if not row.get('external_id') or not row.get('email'):
print(f"Skipping invalid row: {row}")
continue
users.append({
'external_id': row['external_id'].strip(),
'name': row['name'].strip(),
'email': row['email'].strip(),
'phone_number': row.get('phone_number', '').strip(),
'division_id': row.get('division_id', '').strip()
})
return users
def create_user_payload(user_data: Dict[str, Any]) -> UserCreateRequest:
"""Converts a dictionary of user data into a UserCreateRequest object."""
phone_numbers = []
if user_data.get('phone_number'):
phone_numbers.append(PhoneNumber(
type="work",
value=user_data['phone_number'],
primary=True
))
request = UserCreateRequest(
name=user_data['name'],
email=user_data['email'],
external_id=user_data['external_id'],
phone_numbers=phone_numbers if phone_numbers else None,
division_id=user_data.get('division_id')
)
return request
def bulk_create_users(client: PlatformClientBuilder, users_data: List[Dict[str, Any]], delay: float = 0.5) -> List[Dict[str, Any]]:
"""Creates users in Genesys Cloud based on the provided data."""
users_api = UsersApi(client)
results = []
print(f"Starting bulk creation for {len(users_data)} users...")
for i, user_data in enumerate(users_data):
try:
payload = create_user_payload(user_data)
response = users_api.post_users(body=payload)
results.append({
'status': 'success',
'external_id': user_data['external_id'],
'genesys_id': response.id,
'name': user_data['name']
})
print(f"[{i+1}/{len(users_data)}] Created: {user_data['name']} (ID: {response.id})")
except ApiException as e:
if e.status == 409:
results.append({
'status': 'exists',
'external_id': user_data['external_id'],
'name': user_data['name'],
'error': 'User already exists'
})
print(f"[{i+1}/{len(users_data)}] Exists: {user_data['name']}")
elif e.status == 429:
wait_time = 5
print(f"[{i+1}/{len(users_data)}] Rate limited. Waiting {wait_time}s...")
time.sleep(wait_time)
users_data.insert(0, user_data)
continue
else:
results.append({
'status': 'error',
'external_id': user_data['external_id'],
'name': user_data['name'],
'error': str(e.body)
})
print(f"[{i+1}/{len(users_data)}] Error: {user_data['name']} - {e.status}")
except Exception as e:
results.append({
'status': 'error',
'external_id': user_data['external_id'],
'name': user_data['name'],
'error': str(e)
})
print(f"[{i+1}/{len(users_data)}] Unexpected Error: {user_data['name']} - {e}")
if i < len(users_data) - 1:
time.sleep(delay)
return results
def print_results(results: List[Dict[str, Any]]):
"""Prints a summary of the bulk creation results."""
success_count = sum(1 for r in results if r['status'] == 'success')
exists_count = sum(1 for r in results if r['status'] == 'exists')
error_count = sum(1 for r in results if r['status'] == 'error')
print("\n--- Bulk Creation Summary ---")
print(f"Total Processed: {len(results)}")
print(f"Successfully Created: {success_count}")
print(f"Already Existed: {exists_count}")
print(f"Errors: {error_count}")
if error_count > 0:
print("\n--- Failed Users ---")
for r in results:
if r['status'] == 'error':
print(f"External ID: {r['external_id']} | Name: {r['name']} | Error: {r['error']}")
if __name__ == "__main__":
try:
# 1. Authenticate
client = get_platform_client()
# 2. Parse CSV
csv_path = "users.csv"
users_data = parse_users_csv(csv_path)
print(f"Parsed {len(users_data)} users from CSV.")
# 3. Bulk Create
results = bulk_create_users(client, users_data, delay=0.5)
# 4. Report Results
print_results(results)
except Exception as e:
print(f"Fatal error: {e}")
Common Errors & Debugging
Error: 409 Conflict
- Cause: The
external_idprovided in the CSV already exists in Genesys Cloud. - Fix: The script above handles this by logging the user as “exists”. If you want to update existing users instead, you must first retrieve the user by
external_idusingGET /api/v2/users?externalId={id}and then callPUT /api/v2/users/{id}.
Error: 400 Bad Request
- Cause: Invalid data format. Common issues include:
- Email address is not valid.
- Phone number format is incorrect (E.164 format is recommended).
division_idis invalid or the OAuth client lacks access to that division.
- Fix: Validate email and phone formats in the
parse_users_csvfunction before sending to the API. Ensure thedivision_idis correct.
Error: 401 Unauthorized
- Cause: OAuth token is expired or invalid.
- Fix: The SDK handles token refresh automatically. If this error persists, verify that the
GENESYS_CLIENT_IDandGENESYS_CLIENT_SECRETare correct and that the OAuth client is active in Genesys Cloud.
Error: 429 Too Many Requests
- Cause: The API rate limit for the OAuth client has been exceeded.
- Fix: The script above implements a simple backoff strategy. For higher throughput, consider implementing exponential backoff or reducing the number of concurrent requests if using threading.