Fixing INVALID_VALUE Errors When Creating Genesys Cloud Outbound Contact Lists via API
What You Will Build
- You will build a Python script that successfully creates a new outbound contact list in Genesys Cloud CX by correctly formatting the request body.
- This tutorial uses the Genesys Cloud
/api/v2/outbound/contactlistsendpoint and the Python SDKPureCloudPlatformClientV2. - The code is written in Python 3.9+ using the
requestslibrary for raw API calls and the official SDK for structured operations.
Prerequisites
- OAuth Client Type: Confidential Client (Client Credentials Grant) or Public Client (Authorization Code Grant).
- Required Scopes:
outbound:contactlist:create(Essential for creation)outbound:contactlist:view(Useful for verification)
- API Version: Genesys Cloud API v2.
- Language/Runtime: Python 3.9 or higher.
- External Dependencies:
requests: For making HTTP calls.purecloudplatformclientv2: The official Genesys Cloud Python SDK.- Install via pip:
pip install requests purecloudplatformclientv2
Authentication Setup
Before creating a contact list, you must obtain a valid access token. The most common cause of silent failures or confusing errors is using a token with insufficient scopes. Ensure your OAuth client has the outbound:contactlist:create scope assigned.
Here is a robust way to retrieve and cache a token using the Client Credentials flow.
import requests
import json
import time
class GenesysAuth:
def __init__(self, client_id: str, client_secret: str, base_url: str):
self.client_id = client_id
self.client_secret = client_secret
self.base_url = base_url.rstrip('/')
self.token = None
self.token_expiry = 0
def get_access_token(self) -> str:
"""
Retrieves an OAuth access token. Caches it until it expires.
"""
# Return cached token if still valid (subtract 60s for buffer)
if self.token and time.time() < self.token_expiry - 60:
return self.token
token_url = f"{self.base_url}/oauth/token"
payload = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"scope": "outbound:contactlist:create outbound:contactlist:view"
}
headers = {
"Content-Type": "application/json"
}
response = requests.post(token_url, headers=headers, data=payload)
if response.status_code != 200:
raise Exception(f"Failed to get token: {response.status_code} - {response.text}")
data = response.json()
self.token = data["access_token"]
self.token_expiry = time.time() + data["expires_in"]
return self.token
Implementation
Step 1: Understanding the INVALID_VALUE Error
The INVALID_VALUE error (HTTP 400) when POSTing to /api/v2/outbound/contactlists almost always stems from one of three issues:
- Missing Required Fields: The
nameortypeis missing or malformed. - Invalid Contact Source ID: If
typeisFILE, you must provide a validcontactSourceId. IftypeisAPI, this field is ignored or must be null depending on the SDK version, but typically omitted. - Malformed JSON Structure: Extra fields or incorrect nesting.
To fix this, we must construct a minimal, valid JSON payload.
Step 2: Constructing the Correct Request Body
The ContactList object requires specific fields.
name: String. Must be unique within the organization.type: Enum. EitherFILEorAPI.contactSourceId: String (UUID). Required only iftypeisFILE. This ID corresponds to a File Connection created in Genesys Cloud.description: String (Optional).
If you are creating an API-based list (where you push contacts via the API later), you do not need a contactSourceId. If you are creating a File-based list (where Genesys pulls from S3/SFTP), you must have the contactSourceId from a previously created File Connection.
Here is the JSON structure for an API-based contact list, which is the simplest to test:
{
"name": "Test API Contact List",
"type": "API",
"description": "Created via API for testing INVALID_VALUE fixes"
}
Here is the JSON structure for a FILE-based contact list:
{
"name": "Test File Contact List",
"type": "FILE",
"contactSourceId": "12345678-1234-1234-1234-123456789012",
"description": "Linked to an S3 bucket"
}
Step 3: Implementing the API Call with Python Requests
We will first use requests to demonstrate the exact HTTP payload. This helps debug SDK issues by isolating the network layer.
def create_contact_list_raw(auth: GenesysAuth, list_name: str, list_type: str, contact_source_id: str = None):
"""
Creates a contact list using raw HTTP requests.
"""
url = f"{auth.base_url}/api/v2/outbound/contactlists"
token = auth.get_access_token()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# Build the payload
payload = {
"name": list_name,
"type": list_type
}
# Conditional field for FILE type
if list_type == "FILE":
if not contact_source_id:
raise ValueError("contact_source_id is required when type is FILE")
payload["contactSourceId"] = contact_source_id
# Optional description
payload["description"] = f"Created via raw API call at {time.strftime('%Y-%m-%d %H:%M:%S')}"
print(f"Sending POST request to {url}")
print(f"Payload: {json.dumps(payload, indent=2)}")
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 201:
print("Success! Contact list created.")
return response.json()
elif response.status_code == 400:
print(f"Bad Request (400): {response.text}")
# Common INVALID_VALUE causes:
# 1. Name already exists
# 2. Invalid contactSourceId for FILE type
# 3. Missing 'name' or 'type'
raise Exception(f"400 Error: {response.text}")
else:
print(f"Unexpected Status Code: {response.status_code}")
print(f"Response: {response.text}")
raise Exception(f"API Error: {response.status_code}")
Step 4: Implementing with the Official Python SDK
The SDK handles object serialization and error parsing. However, developers often get INVALID_VALUE here because they instantiate the ContactList object incorrectly or pass None for required fields.
from purecloudplatformclientv2 import ApiClient, Configuration, OutboundApi, ContactList
from purecloudplatformclientv2.rest import ApiException
def create_contact_list_sdk(client_id: str, client_secret: str, base_url: str, list_name: str, list_type: str, contact_source_id: str = None):
"""
Creates a contact list using the official Genesys Cloud Python SDK.
"""
# 1. Configure the API Client
configuration = Configuration(
host=base_url,
client_id=client_id,
client_secret=client_secret
)
api_client = ApiClient(configuration)
outbound_api = OutboundApi(api_client)
# 2. Build the ContactList Object
# The SDK enforces types, but you must still provide valid values.
try:
contact_list = ContactList(
name=list_name,
type=list_type,
description="Created via Python SDK"
)
# If type is FILE, set the contactSourceId
if list_type == "FILE":
if not contact_source_id:
raise ValueError("contact_source_id is required for FILE type")
contact_list.contact_source_id = contact_source_id
print(f"Creating contact list with SDK: {contact_list.name}")
# 3. Execute the API Call
# api_response contains the created object
api_response = outbound_api.post_outbound_contactlists(body=contact_list)
print(f"Success! Contact List ID: {api_response.id}")
print(f"Contact List Name: {api_response.name}")
print(f"Contact List Type: {api_response.type}")
return api_response
except ApiException as e:
print(f"SDK Exception: {e.status} - {e.reason}")
print(f"Body: {e.body}")
# Specific handling for INVALID_VALUE
if e.status == 400:
try:
error_body = json.loads(e.body)
if "message" in error_body:
print(f"Error Message: {error_body['message']}")
if "errors" in error_body:
for err in error_body["errors"]:
print(f"Field Error: {err.get('field', 'unknown')} - {err.get('message', 'unknown')}")
except:
pass
raise
except ValueError as ve:
print(f"Validation Error: {ve}")
raise
Complete Working Example
This script combines authentication and the SDK approach. It includes a helper to find an existing File Connection ID if you need to create a FILE-based list, as this is a common stumbling block.
import os
import json
import time
import requests
from purecloudplatformclientv2 import ApiClient, Configuration, OutboundApi, ContactList
from purecloudplatformclientv2.rest import ApiException
# Configuration
GENESYS_BASE_URL = "https://api.mypurecloud.com" # Replace with your region
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
def get_file_connections(api_client: ApiClient) -> list:
"""
Helper to list File Connections. Useful for finding a valid contactSourceId.
"""
outbound_api = OutboundApi(api_client)
try:
# Get all file connections
api_response = outbound_api.get_outbound_fileconnections(page_size=25)
return api_response.entities
except ApiException as e:
print(f"Error fetching file connections: {e}")
return []
def main():
if not CLIENT_ID or not CLIENT_SECRET:
raise Exception("Set GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables.")
# 1. Initialize SDK Configuration
configuration = Configuration(
host=GENESYS_BASE_URL,
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET
)
api_client = ApiClient(configuration)
outbound_api = OutboundApi(api_client)
# 2. Define Contact List Parameters
# OPTION A: API Type (No file source needed)
list_type = "API"
contact_source_id = None
list_name = f"SDK Test List {int(time.time())}"
# OPTION B: FILE Type (Requires a valid contactSourceId)
# To use FILE type, uncomment below and comment out OPTION A
# list_type = "FILE"
# # Find a file connection ID
# file_connections = get_file_connections(api_client)
# if not file_connections:
# raise Exception("No file connections found. Create one in Genesys Cloud first.")
# contact_source_id = file_connections[0].id
# list_name = f"File Test List {int(time.time())}"
# 3. Create the Contact List Object
print(f"Creating Contact List: {list_name} (Type: {list_type})")
contact_list = ContactList(
name=list_name,
type=list_type,
description="Automated test list"
)
if list_type == "FILE":
contact_list.contact_source_id = contact_source_id
# 4. Execute Creation
try:
api_response = outbound_api.post_outbound_contactlists(body=contact_list)
print("-" * 20)
print("SUCCESS")
print(f"ID: {api_response.id}")
print(f"Name: {api_response.name}")
print(f"Type: {api_response.type}")
print(f"State: {api_response.state}")
print(f"Created Date: {api_response.created_date}")
print("-" * 20)
# 5. Cleanup (Optional: Delete the list immediately)
# api_response = outbound_api.delete_outbound_contactlist(contact_list_id=api_response.id)
# print(f"Deleted test list {api_response.id}")
except ApiException as e:
print("-" * 20)
print("FAILURE")
print(f"Status Code: {e.status}")
print(f"Reason: {e.reason}")
print(f"Body: {e.body}")
# Parse detailed error messages
try:
err_data = json.loads(e.body)
if 'errors' in err_data:
print("\nDetailed Errors:")
for err in err_data['errors']:
print(f" - Field: {err.get('field', 'N/A')}")
print(f" - Message: {err.get('message', 'N/A')}")
except:
pass
print("-" * 20)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 400 Bad Request - INVALID_VALUE
Cause 1: Missing contactSourceId for FILE Type
If you set type to FILE but do not provide a valid UUID for contactSourceId, the API returns INVALID_VALUE.
- Fix: Ensure you have created a File Connection in Genesys Cloud (Admin > Outbound > File Connections). Use that connection’s ID.
- Code Check: Verify
contact_list.contact_source_idis set before callingpost_outbound_contactlists.
Cause 2: Duplicate Name
Contact list names must be unique within the organization.
- Fix: Append a timestamp or random suffix to the name during testing.
- Code Check: Use
list_name = f"My List {int(time.time())}".
Cause 3: Invalid type Enum Value
The type field must be exactly FILE or API. Case sensitivity matters.
- Fix: Use
list_type = "API"orlist_type = "FILE". Do not useapiorfile.
Cause 4: Unauthorized (401) or Forbidden (403) Mistaken for 400
Sometimes, if the token is expired, the SDK might throw a generic error.
- Fix: Check the status code explicitly. If it is 401, refresh your token. If it is 403, check that your OAuth client has the
outbound:contactlist:createscope.
Error: SDK AttributeError
Cause: Passing a dictionary instead of a ContactList object to the SDK method.
- Fix: The Genesys Python SDK expects model objects. Do not pass
json.dumps({...})topost_outbound_contactlists. InstantiateContactList(...)instead.