Implementing Dynamic Branching Logic with ASSIGN and IF in NICE CXone Studio
What You Will Build
- You will build a CXone Studio flow that evaluates incoming customer data against business rules to route calls to specific queues or play tailored responses.
- This tutorial uses the NICE CXone Studio API (
/api/v2/studio/...) and the Studio Visual Editor concepts translated into JSON configuration. - The implementation is demonstrated via Python using the
requestslibrary to generate and deploy Studio flow JSON, alongside direct Studio Snippet logic explanations.
Prerequisites
- OAuth Client Type: Confidential Client (Client Credentials Grant).
- Required Scopes:
studio:flow:read,studio:flow:write,studio:snippet:read,studio:snippet:write. - SDK/API Version: NICE CXone Public API v2.
- Language/Runtime: Python 3.9+ with
requestsandpydantic(for validation) or Node.js 18+ withaxios. - External Dependencies:
requests,python-dotenvfor credential management.
Authentication Setup
NICE CXone uses OAuth 2.0 for API authentication. You must obtain an access token before making any Studio API calls. The following Python code demonstrates the standard Client Credentials flow.
import requests
import os
import json
from typing import Optional
class CXoneAuth:
def __init__(self, client_id: str, client_secret: str, auth_url: str = "https://api.mynicecx.com/oauth2/token"):
self.client_id = client_id
self.client_secret = client_secret
self.auth_url = auth_url
self.access_token: Optional[str] = None
def get_token(self) -> str:
"""
Retrieves an OAuth2 access token using Client Credentials.
Returns:
str: The access token.
"""
data = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
response = requests.post(self.auth_url, data=data, headers=headers)
if response.status_code != 200:
raise Exception(f"Authentication failed: {response.status_code} - {response.text}")
token_data = response.json()
self.access_token = token_data.get("access_token")
return self.access_token
# Usage
auth = CXoneAuth(
client_id=os.getenv("CXONE_CLIENT_ID"),
client_secret=os.getenv("CXONE_CLIENT_SECRET")
)
token = auth.get_token()
Implementation
Step 1: Understanding the Studio JSON Schema for ASSIGN and IF
In CXone Studio, flows are defined as JSON. The ASSIGN action sets a variable, and the IF action branches based on a condition. You do not drag-and-drop these in the API; you construct the JSON structure.
The ASSIGN action maps to a node with type: "assign".
The IF action maps to a node with type: "decision" or type: "if".
Below is the raw JSON structure for a simple logic block:
- Assign a variable
is_premiumbased on a DNIS check. - Use an
IFstatement to branch based onis_premium.
{
"id": "flow-uuid-here",
"name": "Premium Routing Logic",
"type": "flow",
"nodes": [
{
"id": "node-assign-premium",
"type": "assign",
"label": "Determine Premium Status",
"inputs": [
{
"id": "input-1",
"label": "Start",
"type": "input"
}
],
"outputs": [
{
"id": "output-1",
"label": "Done",
"type": "output"
}
],
"properties": {
"assignments": [
{
"variable": "is_premium",
"expression": "${DNIS == '18005550199'}",
"type": "boolean"
}
]
}
},
{
"id": "node-check-premium",
"type": "decision",
"label": "Check Premium Status",
"inputs": [
{
"id": "input-1",
"label": "Start",
"type": "input"
}
],
"outputs": [
{
"id": "output-true",
"label": "True",
"type": "output"
},
{
"id": "output-false",
"label": "False",
"type": "output"
}
],
"properties": {
"conditions": [
{
"id": "cond-1",
"expression": "${is_premium == true}",
"outputId": "output-true"
}
],
"defaultOutputId": "output-false"
}
}
],
"links": [
{
"sourceNodeId": "node-assign-premium",
"sourceOutputId": "output-1",
"targetNodeId": "node-check-premium",
"targetInputId": "input-1"
}
]
}
Step 2: Creating the Flow via API
To create this flow, you must POST the JSON to the Studio API. This step ensures the flow exists in your environment.
Endpoint: POST /api/v2/studio/flows
Scope: studio:flow:write
import requests
import os
import json
from typing import Dict, Any
class CXoneStudioClient:
def __init__(self, base_url: str, token: str):
self.base_url = base_url
self.token = token
self.headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
def create_flow(self, flow_payload: Dict[str, Any]) -> Dict[str, Any]:
"""
Creates a new Studio Flow.
Args:
flow_payload: The JSON definition of the flow.
Returns:
The response from the API containing the flow ID.
"""
url = f"{self.base_url}/api/v2/studio/flows"
try:
response = requests.post(url, json=flow_payload, headers=self.headers)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e}")
print(f"Response Body: {e.response.text}")
raise
except requests.exceptions.RequestException as e:
print(f"Request Error: {e}")
raise
# Define the flow payload
flow_json = {
"name": "Dynamic Premium Routing",
"description": "Routes premium customers to a dedicated queue using ASSIGN and IF.",
"type": "flow",
"nodes": [
{
"id": "assign-dnis-check",
"type": "assign",
"label": "Set Premium Flag",
"inputs": [{"id": "in-1", "label": "Start", "type": "input"}],
"outputs": [{"id": "out-1", "label": "Done", "type": "output"}],
"properties": {
"assignments": [
{
"variable": "is_premium",
"expression": "${DNIS == '18005550199'}",
"type": "boolean"
}
]
}
},
{
"id": "decision-premium",
"type": "decision",
"label": "Route Based on Premium",
"inputs": [{"id": "in-1", "label": "Start", "type": "input"}],
"outputs": [
{"id": "out-true", "label": "Premium", "type": "output"},
{"id": "out-false", "label": "Standard", "type": "output"}
],
"properties": {
"conditions": [
{
"id": "cond-premium",
"expression": "${is_premium == true}",
"outputId": "out-true"
}
],
"defaultOutputId": "out-false"
}
},
{
"id": "queue-premium",
"type": "queue",
"label": "Premium Queue",
"inputs": [{"id": "in-1", "label": "Start", "type": "input"}],
"outputs": [{"id": "out-1", "label": "Done", "type": "output"}],
"properties": {
"queueId": "premium-queue-id-placeholder",
"timeout": 60000
}
},
{
"id": "queue-standard",
"type": "queue",
"label": "Standard Queue",
"inputs": [{"id": "in-1", "label": "Start", "type": "input"}],
"outputs": [{"id": "out-1", "label": "Done", "type": "output"}],
"properties": {
"queueId": "standard-queue-id-placeholder",
"timeout": 60000
}
}
],
"links": [
{
"sourceNodeId": "assign-dnis-check",
"sourceOutputId": "out-1",
"targetNodeId": "decision-premium",
"targetInputId": "in-1"
},
{
"sourceNodeId": "decision-premium",
"sourceOutputId": "out-true",
"targetNodeId": "queue-premium",
"targetInputId": "in-1"
},
{
"sourceNodeId": "decision-premium",
"sourceOutputId": "out-false",
"targetNodeId": "queue-standard",
"targetInputId": "in-1"
}
]
}
# Initialize client and create flow
studio_client = CXoneStudioClient(
base_url="https://api.mynicecx.com",
token=token
)
try:
result = studio_client.create_flow(flow_json)
print(f"Flow Created Successfully: {result.get('id')}")
except Exception as e:
print(f"Failed to create flow: {e}")
Step 3: Advanced Branching with Studio Snippets
While the JSON API is powerful for bulk operations, complex logic is often easier to manage using Studio Snippets. Snippets allow you to write JavaScript-like logic within the flow.
To use a snippet, you create a snippet resource and reference it in the ASSIGN or IF node.
1. Create the Snippet
Endpoint: POST /api/v2/studio/snippets
Scope: studio:snippet:write
snippet_payload = {
"name": "Calculate Priority Score",
"description": "Calculates a priority score based on customer tenure and tier.",
"type": "snippet",
"code": """
// Input variables: customerTenure (number), customerTier (string)
// Output: priorityScore (number)
let score = 10;
if (customerTier === 'Gold') {
score += 50;
} else if (customerTier === 'Silver') {
score += 25;
}
if (customerTenure > 5) {
score += 10;
}
return score;
"""
}
try:
snippet_result = requests.post(
f"{studio_client.base_url}/api/v2/studio/snippets",
json=snippet_payload,
headers=studio_client.headers
)
snippet_result.raise_for_status()
snippet_id = snippet_result.json().get("id")
print(f"Snippet Created: {snippet_id}")
except Exception as e:
print(f"Snippet creation failed: {e}")
2. Reference the Snippet in an ASSIGN Node
Modify the ASSIGN node in your flow JSON to call the snippet.
{
"id": "assign-priority",
"type": "assign",
"label": "Calculate Priority",
"inputs": [{"id": "in-1", "label": "Start", "type": "input"}],
"outputs": [{"id": "out-1", "label": "Done", "type": "output"}],
"properties": {
"assignments": [
{
"variable": "priorityScore",
"expression": "${snippet('Calculate Priority Score', customerTenure: ANI_TENURE, customerTier: CUSTOMER_TIER)}",
"type": "number"
}
]
}
}
3. Branch on the Snippet Result
Use an IF node to branch based on the returned score.
{
"id": "decision-high-priority",
"type": "decision",
"label": "High Priority Check",
"inputs": [{"id": "in-1", "label": "Start", "type": "input"}],
"outputs": [
{"id": "out-high", "label": "High Priority", "type": "output"},
{"id": "out-normal", "label": "Normal", "type": "output"}
],
"properties": {
"conditions": [
{
"id": "cond-high",
"expression": "${priorityScore > 60}",
"outputId": "out-high"
}
],
"defaultOutputId": "out-normal"
}
}
Complete Working Example
The following Python script combines authentication, snippet creation, and flow creation into a single executable module. Replace the placeholder credentials and queue IDs with your actual environment values.
import requests
import os
import json
from typing import Dict, Any, Optional
class CXoneStudioManager:
BASE_URL = "https://api.mynicecx.com"
def __init__(self, client_id: str, client_secret: str):
self.client_id = client_id
self.client_secret = client_secret
self.token: Optional[str] = None
self.headers = {}
def authenticate(self) -> bool:
"""Authenticates and stores the access token."""
data = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
response = requests.post(f"{self.BASE_URL}/oauth2/token", data=data)
if response.status_code == 200:
self.token = response.json()["access_token"]
self.headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}
return True
else:
raise Exception(f"Auth failed: {response.text}")
def create_snippet(self, name: str, code: str) -> str:
"""Creates a Studio Snippet and returns its ID."""
payload = {
"name": name,
"type": "snippet",
"code": code
}
response = requests.post(
f"{self.BASE_URL}/api/v2/studio/snippets",
json=payload,
headers=self.headers
)
response.raise_for_status()
return response.json()["id"]
def create_flow(self, flow_json: Dict[str, Any]) -> str:
"""Creates a Studio Flow and returns its ID."""
response = requests.post(
f"{self.BASE_URL}/api/v2/studio/flows",
json=flow_json,
headers=self.headers
)
response.raise_for_status()
return response.json()["id"]
def main():
# 1. Setup Authentication
manager = CXoneStudioManager(
client_id=os.getenv("CXONE_CLIENT_ID"),
client_secret=os.getenv("CXONE_CLIENT_SECRET")
)
try:
manager.authenticate()
except Exception as e:
print(f"Authentication Error: {e}")
return
# 2. Define and Create Snippet
snippet_code = """
// Determines if a customer is VIP based on tier and tenure
let isVip = false;
if (tier === 'Platinum' || tier === 'Gold') {
isVip = true;
} else if (tenure > 10) {
isVip = true;
}
return isVip;
"""
try:
snippet_id = manager.create_snippet("Check VIP Status", snippet_code)
print(f"Snippet Created: {snippet_id}")
except Exception as e:
print(f"Snippet Creation Error: {e}")
return
# 3. Define Flow JSON
# Note: In a real scenario, you would fetch Queue IDs via the Queue API
# Here we use placeholders. Ensure these IDs exist in your environment.
premium_queue_id = "YOUR_PREMIUM_QUEUE_ID"
standard_queue_id = "YOUR_STANDARD_QUEUE_ID"
flow_json = {
"name": "VIP Routing Flow",
"description": "Routes VIPs to premium queue using Snippet logic.",
"type": "flow",
"nodes": [
{
"id": "assign-vip-check",
"type": "assign",
"label": "Check VIP Status",
"inputs": [{"id": "in-1", "label": "Start", "type": "input"}],
"outputs": [{"id": "out-1", "label": "Done", "type": "output"}],
"properties": {
"assignments": [
{
"variable": "is_vip",
"expression": "${snippet('Check VIP Status', tier: CUSTOMER_TIER, tenure: CUSTOMER_TENURE)}",
"type": "boolean"
}
]
}
},
{
"id": "decision-vip",
"type": "decision",
"label": "VIP Decision",
"inputs": [{"id": "in-1", "label": "Start", "type": "input"}],
"outputs": [
{"id": "out-vip", "label": "VIP", "type": "output"},
{"id": "out-standard", "label": "Standard", "type": "output"}
],
"properties": {
"conditions": [
{
"id": "cond-vip",
"expression": "${is_vip == true}",
"outputId": "out-vip"
}
],
"defaultOutputId": "out-standard"
}
},
{
"id": "queue-vip",
"type": "queue",
"label": "VIP Queue",
"inputs": [{"id": "in-1", "label": "Start", "type": "input"}],
"outputs": [{"id": "out-1", "label": "Done", "type": "output"}],
"properties": {
"queueId": premium_queue_id,
"timeout": 60000
}
},
{
"id": "queue-standard",
"type": "queue",
"label": "Standard Queue",
"inputs": [{"id": "in-1", "label": "Start", "type": "input"}],
"outputs": [{"id": "out-1", "label": "Done", "type": "output"}],
"properties": {
"queueId": standard_queue_id,
"timeout": 60000
}
}
],
"links": [
{
"sourceNodeId": "assign-vip-check",
"sourceOutputId": "out-1",
"targetNodeId": "decision-vip",
"targetInputId": "in-1"
},
{
"sourceNodeId": "decision-vip",
"sourceOutputId": "out-vip",
"targetNodeId": "queue-vip",
"targetInputId": "in-1"
},
{
"sourceNodeId": "decision-vip",
"sourceOutputId": "out-standard",
"targetNodeId": "queue-standard",
"targetInputId": "in-1"
}
]
}
# 4. Create Flow
try:
flow_id = manager.create_flow(flow_json)
print(f"Flow Created Successfully: {flow_id}")
except Exception as e:
print(f"Flow Creation Error: {e}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 400 Bad Request - Invalid JSON Structure
- What causes it: The Studio API is strict about node IDs, input/output IDs, and link references. If a
linksobject references asourceNodeIdthat does not exist in thenodesarray, the API returns a 400 error. - How to fix it: Validate that every
sourceNodeIdandtargetNodeIdin thelinksarray matches anidin thenodesarray. EnsuresourceOutputIdandtargetInputIdmatch theids defined in theinputsandoutputsarrays of the respective nodes. - Code Fix: Use a JSON schema validator or manually cross-reference IDs before posting.
Error: 403 Forbidden - Insufficient Scope
- What causes it: The OAuth token does not have the
studio:flow:writeorstudio:snippet:writescope. - How to fix it: Update your OAuth Client configuration in the CXone Admin Console to include the required Studio scopes. Regenerate the token.
- Code Fix: Check the token response for the
scopefield.
Error: 500 Internal Server Error - Snippet Syntax Error
- What causes it: The JavaScript code in the Snippet contains a syntax error. The Studio API may not always provide detailed line numbers in the error response.
- How to fix it: Test the Snippet code in a local JavaScript environment or browser console before embedding it in the API call.
- Code Fix: Add try-catch blocks in your snippet code to handle runtime errors gracefully.