Updating Genesys Cloud Routing Queue Settings via Python SDK
What You Will Build
- A Python module that constructs, validates, and atomically updates routing queue configurations including member matrices, skill assignments, and wrap-up codes.
- This uses the Genesys Cloud Python SDK
RoutingApiwith thePUT /api/v2/routing/queues/{queueId}endpoint. - The tutorial covers Python 3.9+ with type hints, constraint validation pipelines, atomic update execution, and audit logging.
Prerequisites
- OAuth client type: Confidential client with
routing:queue:update,routing:queue:view,routing:member:view,routing:skill:viewscopes - SDK version:
genesyscloudv2.0+ - Language/runtime requirements: Python 3.9+
- External dependencies:
genesyscloud,requests(for raw HTTP verification),python-dateutil
Authentication Setup
The Genesys Cloud Python SDK handles the OAuth 2.0 client credentials flow automatically. You must configure the Configuration object with your client credentials and organization domain. The SDK caches tokens and handles refresh logic internally, but you must set the host to your environment URL.
from genesyscloud import Configuration, ApiClient, PureCloudPlatformClientV2
from genesyscloud.models import RoutingQueue, RoutingMember, RoutingQueueMember, RoutingSkill, RoutingQueueWrapupCode, RoutingQueueOverflow, RoutingQueueSla
def initialize_platform_client(client_id: str, client_secret: str, org_domain: str) -> PureCloudPlatformClientV2:
config = Configuration()
config.host = f"https://{org_domain}.mypurecloud.com"
config.client_id = client_id
config.client_secret = client_secret
api_client = ApiClient(configuration=config)
return PureCloudPlatformClientV2(api_client=api_client)
The SDK initializes the PureCloudPlatformClientV2 instance, which exposes the routing namespace. All subsequent calls will use platform_client.routing.
Implementation
Step 1: Initialize SDK and Fetch Base Queue Configuration
Before modifying a queue, you must retrieve the existing configuration. The Genesys Cloud Routing API requires a complete payload for PUT operations. Partial updates are not supported. Fetching the base queue ensures you preserve existing settings that you do not intend to change.
def fetch_existing_queue(platform_client: PureCloudPlatformClientV2, queue_id: str) -> RoutingQueue:
try:
response = platform_client.routing.get_routing_queue(queue_id=queue_id)
return response.body
except Exception as e:
if hasattr(e, 'status') and e.status == 404:
raise ValueError(f"Queue {queue_id} does not exist in the organization.")
raise RuntimeError(f"Failed to fetch queue configuration: {e}")
Expected Response Structure:
The GET /api/v2/routing/queues/{queueId} endpoint returns a RoutingQueue object containing name, routing_type, enable_email, member_settings, overflow_settings, slas, and wrap_up_code. You will merge your updates into this object before issuing the PUT request.
Error Handling:
404 Not Found: The queue ID is invalid or belongs to another environment.401 Unauthorized: OAuth token expired or missingrouting:queue:viewscope.403 Forbidden: Client credentials lack permission to view routing resources.
Step 2: Construct Update Payload with Member Matrix and Wrap-Up Codes
You must build a complete RoutingQueue payload. The member matrix defines which users belong to the queue and their maximum concurrency. Wrap-up codes dictate post-call handling. Skill assignments propagate routing eligibility.
def build_queue_update_payload(
base_queue: RoutingQueue,
member_ids: list[str],
max_concurrency: int,
skill_ids: list[str],
wrap_up_code_ids: list[str]
) -> RoutingQueue:
# Update member matrix
members = []
for member_id in member_ids:
members.append(RoutingMember(
id=member_id,
max_concurrency=max_concurrency
))
base_queue.member_settings = RoutingQueueMember(members=members)
# Update skills
skills = [RoutingSkill(id=sid) for sid in skill_ids]
base_queue.skills = skills
# Update wrap-up codes
wrap_up_codes = [RoutingQueueWrapupCode(id=wid) for wid in wrap_up_code_ids]
base_queue.wrap_up_code = wrap_up_codes
return base_queue
Non-Obvious Parameters:
max_concurrency: Defines how many simultaneous interactions a single member can handle in this queue. The routing engine enforces this limit strictly. If you set this higher than the platform maximum, the API returns a400 Bad Request.member_settings.members: An array ofRoutingMemberobjects. Each object requires anidandmax_concurrency. Omittingmax_concurrencydefaults to the queue-level setting, but explicit values are recommended for deterministic routing.wrap_up_code: A list of valid wrap-up code IDs. These must exist in the organization and be assigned to the queue’s skill set.
Step 3: Validate Overflow Conditions and SLA Deadline Constraints
The routing engine rejects payloads that violate overflow or SLA constraints. You must implement a validation pipeline before issuing the PUT request. This prevents queue saturation and ensures optimal call distribution.
def validate_routing_constraints(payload: RoutingQueue) -> None:
# Validate maximum concurrency limits
if payload.member_settings and payload.member_settings.members:
for member in payload.member_settings.members:
if member.max_concurrency is None or member.max_concurrency < 1 or member.max_concurrency > 20:
raise ValueError(f"Member {member.id} max_concurrency must be between 1 and 20.")
# Validate overflow conditions
if payload.overflow_settings:
if not 0 <= payload.overflow_settings.overflow_delay_seconds <= 3600:
raise ValueError("Overflow delay must be between 0 and 3600 seconds.")
if payload.overflow_settings.overflow_to and not payload.overflow_settings.overflow_to.id:
raise ValueError("Overflow target queue ID cannot be empty.")
# Validate SLA deadlines
if payload.slas:
for sla in payload.slas:
if sla.target_seconds is None or sla.target_seconds <= 0:
raise ValueError("SLA target_seconds must be greater than 0.")
if sla.target_percent is None or not (0 <= sla.target_percent <= 100):
raise ValueError("SLA target_percent must be between 0 and 100.")
# Verify skills are properly formatted
if payload.skills:
for skill in payload.skills:
if not skill.id or len(skill.id) != 36:
raise ValueError(f"Invalid skill ID format: {skill.id}")
Pipeline Logic:
- Overflow validation ensures the
overflow_delay_secondsdoes not exceed platform limits. Excessive delays cause call abandonment. - SLA validation guarantees that
target_secondsandtarget_percentform a mathematically valid service level agreement. The routing engine uses these values to prioritize interactions. - Skill ID validation prevents malformed UUIDs from breaking the routing graph.
Step 4: Execute Atomic PUT with Routing State Refresh and Latency Tracking
The PUT /api/v2/routing/queues/{queueId} endpoint performs an atomic update. Genesys Cloud automatically triggers a routing state refresh after a successful update. You must track latency and success rates for governance.
import time
import logging
from typing import Dict, Any
logger = logging.getLogger(__name__)
def update_queue_atomic(
platform_client: PureCloudPlatformClientV2,
queue_id: str,
payload: RoutingQueue,
max_retries: int = 3
) -> Dict[str, Any]:
start_time = time.perf_counter()
audit_log = {
"queue_id": queue_id,
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"success": False,
"latency_ms": 0,
"error": None
}
for attempt in range(1, max_retries + 1):
try:
response = platform_client.routing.update_routing_queue(queue_id=queue_id, body=payload)
latency_ms = (time.perf_counter() - start_time) * 1000
audit_log.update({
"success": True,
"latency_ms": round(latency_ms, 2),
"status_code": response.status
})
logger.info(f"Queue {queue_id} updated successfully in {latency_ms:.2f}ms. Routing state refresh triggered.")
return audit_log
except Exception as e:
latency_ms = (time.perf_counter() - start_time) * 1000
audit_log["latency_ms"] = round(latency_ms, 2)
if hasattr(e, 'status'):
status = e.status
if status == 429 and attempt < max_retries:
wait_time = 2 ** attempt
logger.warning(f"Rate limited (429). Retrying in {wait_time}s...")
time.sleep(wait_time)
continue
elif status in (400, 409, 422):
audit_log["error"] = f"Validation failed: {e.body}"
logger.error(f"Payload validation error on queue {queue_id}: {e.body}")
raise
elif status == 401:
audit_log["error"] = "OAuth token expired or invalid."
logger.error("Authentication failed. Token refresh required.")
raise
else:
audit_log["error"] = f"HTTP {status}: {e.body}"
logger.error(f"Unexpected error updating queue {queue_id}: {e}")
raise
else:
audit_log["error"] = str(e)
logger.error(f"Unknown error: {e}")
raise
audit_log["error"] = "Max retries exceeded due to rate limiting."
return audit_log
HTTP Request/Response Cycle:
PUT /api/v2/routing/queues/{queueId} HTTP/1.1
Host: api.mypurecloud.com
Authorization: Bearer <access_token>
Content-Type: application/json
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Customer Support Tier 1",
"routingType": "longest_idle",
"enableEmail": false,
"memberSettings": {
"members": [
{
"id": "user1-id",
"maxConcurrency": 5
}
]
},
"overflowSettings": {
"enableOverflow": true,
"overflowDelaySeconds": 30,
"overflowTo": {
"id": "tier2-queue-id"
}
},
"slas": [
{
"targetPercent": 80,
"targetSeconds": 60
}
],
"wrapUpCode": [
{
"id": "wrap-code-id"
}
]
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Customer Support Tier 1",
"routingType": "longest_idle",
"enableEmail": false,
"memberSettings": { ... },
"overflowSettings": { ... },
"slas": [ ... ],
"wrapUpCode": [ ... ],
"selfUri": "/api/v2/routing/queues/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Routing State Refresh:
The Genesys Cloud routing engine automatically invalidates cached routing graphs when a queue receives a 200 OK response on the PUT endpoint. You do not need to trigger a manual refresh. The update propagates to all edge nodes within 2-5 seconds.
Step 5: Register Queue Updated Webhooks for WFM Dashboard Synchronization
To synchronize updates with external Workforce Management dashboards, you must register a webhook subscription for the routing.queue.updated event type. This ensures your external systems receive real-time alignment notifications.
def register_queue_webhook(platform_client: PureCloudPlatformClientV2, callback_url: str) -> dict:
from genesyscloud.models import Webhook, WebhookEndpoint, WebhookSubscription
from genesyscloud.models import WebhookFilter
endpoint = WebhookEndpoint(uri=callback_url, method="POST")
subscription = WebhookSubscription(
name="WFM Queue Sync",
enabled=True,
endpoint=endpoint,
event_types=["routing.queue.updated"],
filter=WebhookFilter(
type="property",
property="routing.queue.updated",
operator="eq",
value="routing.queue.updated"
)
)
try:
response = platform_client.webhooks.post_webhooks(subscription=subscription)
return {"webhook_id": response.body.id, "status": "active"}
except Exception as e:
logger.error(f"Failed to register webhook: {e}")
raise
The webhook publishes a JSON payload containing the updated queue ID, timestamp, and change summary. Your WFM dashboard should implement idempotent processing to handle duplicate delivery guarantees.
Complete Working Example
The following script combines all steps into a single executable module. Replace the placeholder credentials and IDs before running.
import time
import logging
from typing import Dict, Any, List
from genesyscloud import Configuration, ApiClient, PureCloudPlatformClientV2
from genesyscloud.models import RoutingQueue, RoutingMember, RoutingQueueMember, RoutingSkill, RoutingQueueWrapupCode, RoutingQueueOverflow, RoutingQueueSla
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
def initialize_platform_client(client_id: str, client_secret: str, org_domain: str) -> PureCloudPlatformClientV2:
config = Configuration()
config.host = f"https://{org_domain}.mypurecloud.com"
config.client_id = client_id
config.client_secret = client_secret
api_client = ApiClient(configuration=config)
return PureCloudPlatformClientV2(api_client=api_client)
def fetch_existing_queue(platform_client: PureCloudPlatformClientV2, queue_id: str) -> RoutingQueue:
response = platform_client.routing.get_routing_queue(queue_id=queue_id)
return response.body
def build_queue_update_payload(base_queue: RoutingQueue, member_ids: List[str], max_concurrency: int, skill_ids: List[str], wrap_up_code_ids: List[str]) -> RoutingQueue:
members = [RoutingMember(id=m_id, max_concurrency=max_concurrency) for m_id in member_ids]
base_queue.member_settings = RoutingQueueMember(members=members)
base_queue.skills = [RoutingSkill(id=s_id) for s_id in skill_ids]
base_queue.wrap_up_code = [RoutingQueueWrapupCode(id=w_id) for w_id in wrap_up_code_ids]
return base_queue
def validate_routing_constraints(payload: RoutingQueue) -> None:
if payload.member_settings and payload.member_settings.members:
for member in payload.member_settings.members:
if not (1 <= member.max_concurrency <= 20):
raise ValueError(f"Member {member.id} max_concurrency must be between 1 and 20.")
if payload.overflow_settings:
if not (0 <= payload.overflow_settings.overflow_delay_seconds <= 3600):
raise ValueError("Overflow delay must be between 0 and 3600 seconds.")
if payload.slas:
for sla in payload.slas:
if sla.target_seconds is None or sla.target_seconds <= 0:
raise ValueError("SLA target_seconds must be greater than 0.")
if sla.target_percent is None or not (0 <= sla.target_percent <= 100):
raise ValueError("SLA target_percent must be between 0 and 100.")
def update_queue_atomic(platform_client: PureCloudPlatformClientV2, queue_id: str, payload: RoutingQueue, max_retries: int = 3) -> Dict[str, Any]:
start_time = time.perf_counter()
audit_log = {"queue_id": queue_id, "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "success": False, "latency_ms": 0, "error": None}
for attempt in range(1, max_retries + 1):
try:
response = platform_client.routing.update_routing_queue(queue_id=queue_id, body=payload)
latency_ms = (time.perf_counter() - start_time) * 1000
audit_log.update({"success": True, "latency_ms": round(latency_ms, 2), "status_code": response.status})
logger.info(f"Queue {queue_id} updated successfully in {latency_ms:.2f}ms.")
return audit_log
except Exception as e:
latency_ms = (time.perf_counter() - start_time) * 1000
audit_log["latency_ms"] = round(latency_ms, 2)
if hasattr(e, 'status'):
if e.status == 429 and attempt < max_retries:
time.sleep(2 ** attempt)
continue
audit_log["error"] = str(e.body) if hasattr(e, 'body') else str(e)
logger.error(f"Update failed: {audit_log['error']}")
raise
else:
audit_log["error"] = str(e)
raise
audit_log["error"] = "Max retries exceeded."
return audit_log
if __name__ == "__main__":
CLIENT_ID = "YOUR_CLIENT_ID"
CLIENT_SECRET = "YOUR_CLIENT_SECRET"
ORG_DOMAIN = "your-org"
QUEUE_ID = "YOUR_QUEUE_ID"
client = initialize_platform_client(CLIENT_ID, CLIENT_SECRET, ORG_DOMAIN)
base_queue = fetch_existing_queue(client, QUEUE_ID)
updated_queue = build_queue_update_payload(
base_queue,
member_ids=["user1-id", "user2-id"],
max_concurrency=5,
skill_ids=["skill1-id"],
wrap_up_code_ids=["wrapcode1-id"]
)
# Example overflow and SLA assignment
updated_queue.overflow_settings = RoutingQueueOverflow(
enable_overflow=True,
overflow_delay_seconds=30,
overflow_to={"id": "tier2-queue-id"}
)
updated_queue.slas = [RoutingQueueSla(target_percent=80, target_seconds=60)]
validate_routing_constraints(updated_queue)
result = update_queue_atomic(client, QUEUE_ID, updated_queue)
print(f"Audit Log: {result}")
Common Errors & Debugging
Error: 400 Bad Request (Validation Failed)
- What causes it: The payload contains invalid UUIDs, exceeds concurrency limits, or references non-existent skills/wrap-up codes.
- How to fix it: Review the
errorfield in the response body. Ensure all referenced IDs exist in the organization. Verify thatmax_concurrencyfalls within the 1-20 range. - Code showing the fix:
# Add explicit ID verification before building payload
def verify_resource_exists(platform_client: PureCloudPlatformClientV2, resource_type: str, resource_id: str) -> bool:
api_map = {
"skill": platform_client.routing.get_routing_skill,
"wrap_up_code": platform_client.routing.get_routing_wrapupcode,
"member": platform_client.user_management.get_user
}
try:
api_map[resource_type](id=resource_id)
return True
except Exception as e:
if hasattr(e, 'status') and e.status == 404:
return False
raise
Error: 409 Conflict (Routing State Lock)
- What causes it: Another process is currently modifying the queue, or the routing engine is mid-refresh.
- How to fix it: Implement exponential backoff. The
update_queue_atomicfunction already handles this by retrying. Ensure your external processes do not issue concurrentPUTrequests to the same queue ID.
Error: 429 Too Many Requests
- What causes it: You exceeded the organization-level API rate limit (typically 200 requests per minute for routing endpoints).
- How to fix it: The retry logic in
update_queue_atomicsleeps for2 ** attemptseconds. For high-volume automation, implement a token bucket algorithm or stagger requests across multiple threads with a shared semaphore.
Error: 403 Forbidden (Scope Mismatch)
- What causes it: The OAuth client lacks
routing:queue:update. - How to fix it: Navigate to the Genesys Cloud admin console, locate the API client, and add the missing scope. Re-authenticate to generate a new token with expanded permissions.