Python SDK bulk user creation from CSV throwing 400 on email validation

Trying to script a bulk user import from a CSV file using the Genesys Cloud Python SDK. Reading rows with csv.DictReader and mapping fields to CreateUserRequest. The first row works fine, but subsequent calls to users_api.create_user(body=user_req) crash with a 400 Bad Request. The error payload says Email is not valid. Debug prints show the emails are clean strings. Am I hitting a rate limit or is the SDK batching requests weirdly?

Don’t hammer create_user in a tight loop. The 400 isn’t just validation noise; it’s often the API rejecting rapid-fire identical headers or transient state locks on the email domain before it fully propagates. Also, CreateUserRequest in the Python SDK is strict about nulls vs empty strings. If your CSV has trailing whitespace or hidden characters, the validator fails hard.

Here’s a safer pattern using the PureCloudPlatformClientV2 client directly. We add a small delay and explicit error handling. This isn’t “best practice” for massive imports (use the bulk endpoint if you have 100+ users), but it works for small batches without needing a dedicated import job.

import time
import csv
from genesyscloud import Configuration, ApiClient, UsersApi

# Setup client
config = Configuration()
config.access_token = 'YOUR_ACCESS_TOKEN'
api_client = ApiClient(configuration=config)
users_api = UsersApi(api_client)

with open('users.csv', 'r') as f:
 reader = csv.DictReader(f)
 for row in reader:
 # Strip whitespace explicitly. SDK doesn't always do this.
 email = row['email'].strip()
 name = row['name'].strip()
 
 if not email or not name:
 print(f"Skipping invalid row: {row}")
 continue

 try:
 # Build the request object
 from genesyscloud.users.models.create_user_request import CreateUserRequest
 user_req = CreateUserRequest(
 email=email,
 name=name,
 email_notification=True, # Default to true to avoid null issues
 phone_numbers=[] # Empty list, not None
 )
 
 # Create the user
 response = users_api.create_user(body=user_req)
 print(f"Created user: {response.id}")
 
 # Small delay to avoid rate limiting and allow DB propagation
 time.sleep(0.5) 
 
 except Exception as e:
 # Log the specific error code and message
 print(f"Failed for {email}: {e.status_code} - {e.reason}")
 # Optional: break on first error to debug
 break

The time.sleep is the key here. It prevents the backend from seeing your requests as a DDoS attack pattern, which sometimes triggers stricter validation rules. If you’re still getting 400s, print the raw user_req JSON before sending it. You’ll likely see a null field that the SDK didn’t strip out.

Also, check if the email domain is actually provisioned in your org. If it’s not, the API rejects it immediately, regardless of syntax.