Implementing Branching Logic with ASSIGN and IF in NICE CXone Studio
What You Will Build
- A CXone Studio flow that evaluates an incoming customer attribute and routes the interaction to different disposition queues based on the value.
- This tutorial uses the NICE CXone Studio JSON configuration API to define the logic programmatically.
- The implementation is demonstrated using Python with the
requestslibrary to construct and deploy the Studio flow JSON.
Prerequisites
- OAuth Client Type: Application (Client Credentials) or User (Authorization Code).
- Required Scopes:
studio:read,studio:write,flows:read,flows:write. - SDK/API Version: CXone Studio REST API (v1).
- Language/Runtime: Python 3.9+.
- External Dependencies:
requests,python-dotenv(for credential management).
Authentication Setup
CXone uses OAuth 2.0 for API authentication. Before manipulating Studio flows, you must obtain a valid access token. This example uses the Client Credentials flow, which is standard for server-to-server integrations.
import requests
import os
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
CLIENT_ID = os.getenv("CXONE_CLIENT_ID")
CLIENT_SECRET = os.getenv("CXONE_CLIENT_SECRET")
TENANT_ID = os.getenv("CXONE_TENANT_ID")
def get_access_token() -> str:
"""
Retrieves an OAuth2 access token from NICE CXone.
Returns the token string or raises an exception on failure.
"""
url = f"https://{TENANT_ID}.api.nicecxone.com/oauth/token"
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
}
try:
response = requests.post(url, headers=headers, data=data)
response.raise_for_status()
token_data = response.json()
return token_data["access_token"]
except requests.exceptions.HTTPError as http_err:
print(f"HTTP error occurred: {http_err}")
raise
except Exception as err:
print(f"Other error occurred: {err}")
raise
# Execute authentication
access_token = get_access_token()
auth_header = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
The access_token is valid for one hour. In a production application, implement a refresh logic or cache the token until expiration. For this tutorial, we assume a single session execution.
Implementation
Step 1: Define the Flow Structure and ASSIGN Action
Studio flows are defined as a directed graph of nodes. To implement branching, you first need a variable to branch on. The ASSIGN action sets the value of a variable. This variable can come from an attribute, a system variable, or a literal value.
We will create a flow that assigns the value of an incoming attribute customer_tier to a local flow variable tier_value. If the attribute does not exist, it defaults to "standard".
The Studio API requires a specific JSON structure. The actions array contains the logic. Each action has a type and specific properties.
def create_assign_action() -> dict:
"""
Creates an ASSIGN action that sets 'tier_value' based on 'customer_tier'.
"""
assign_action = {
"id": "assign_tier",
"type": "ASSIGN",
"properties": {
"variableName": "tier_value",
"expression": {
"type": "FUNCTION",
"functionName": "ifThenElse",
"arguments": [
{
"type": "BOOLEAN",
"value": {
"type": "FUNCTION",
"functionName": "hasAttribute",
"arguments": [
{
"type": "STRING",
"value": "customer_tier"
}
]
}
},
{
"type": "ATTRIBUTE",
"attributeName": "customer_tier"
},
{
"type": "STRING",
"value": "standard"
}
]
}
}
}
return assign_action
assign_node = create_assign_action()
Key Concepts:
variableName: The internal name of the variable within the flow context.expression: CXone Studio uses a functional expression language. TheifThenElsefunction takes three arguments: condition, true_value, false_value.hasAttribute: Checks if an interaction attribute exists. This prevents errors when the attribute is null or undefined.
Step 2: Implement the IF Action for Branching
The IF action evaluates a condition and directs the flow to one of two branches: true or false. You must define the trueBranch and falseBranch IDs. These IDs must correspond to other nodes in the flow definition.
We will check if tier_value equals "premium".
def create_if_action() -> dict:
"""
Creates an IF action that checks if tier_value is 'premium'.
"""
if_action = {
"id": "check_tier",
"type": "IF",
"properties": {
"condition": {
"type": "FUNCTION",
"functionName": "equals",
"arguments": [
{
"type": "VARIABLE",
"variableName": "tier_value"
},
{
"type": "STRING",
"value": "premium"
}
]
},
"trueBranch": "route_premium",
"falseBranch": "route_standard"
}
}
return if_action
if_node = create_if_action()
Key Concepts:
condition: A boolean expression. Here,equalscompares the variabletier_valueagainst the literal string"premium".trueBranch: The ID of the node to execute if the condition is true.falseBranch: The ID of the node to execute if the condition is false.
Step 3: Define the Branch Targets (Route Actions)
The IF action points to route_premium and route_standard. These must be defined as nodes. In Studio, routing is typically done via a ROUTE action. This action sends the interaction to a specific queue or agent group.
def create_route_actions() -> dict:
"""
Creates two ROUTE actions: one for premium and one for standard.
"""
route_premium = {
"id": "route_premium",
"type": "ROUTE",
"properties": {
"queueId": "premium-support-queue-id", # Replace with actual Queue ID
"timeoutSeconds": 60
}
}
route_standard = {
"id": "route_standard",
"type": "ROUTE",
"properties": {
"queueId": "standard-support-queue-id", # Replace with actual Queue ID
"timeoutSeconds": 60
}
}
return {
"route_premium": route_premium,
"route_standard": route_standard
}
route_nodes = create_route_actions()
Note: You must replace "premium-support-queue-id" and "standard-support-queue-id" with actual UUIDs from your CXone tenant. You can retrieve these via the /api/v2/routing/queues endpoint.
Step 4: Assemble the Complete Flow JSON
Studio flows require a nodes array and a startNodeId. The flow begins at the startNodeId.
def assemble_flow_json() -> dict:
"""
Assembles the complete Studio flow JSON structure.
"""
# Retrieve nodes
assign_node = create_assign_action()
if_node = create_if_action()
route_premium = route_nodes["route_premium"]
route_standard = route_nodes["route_standard"]
# Define the flow structure
flow_data = {
"name": "Tiered Routing Logic",
"description": "Routes calls based on customer tier attribute.",
"version": 1,
"nodes": [
assign_node,
if_node,
route_premium,
route_standard
],
"startNodeId": assign_node["id"]
}
return flow_data
flow_payload = assemble_flow_json()
Step 5: Deploy the Flow via API
Now that the JSON is constructed, we send it to the CXone Studio API. The endpoint for creating a flow is /api/v2/studio/flows.
def deploy_flow(flow_data: dict) -> dict:
"""
Deploys the flow to CXone Studio.
"""
url = f"https://{TENANT_ID}.api.nicecxone.com/api/v2/studio/flows"
try:
response = requests.post(url, headers=auth_header, json=flow_data)
response.raise_for_status()
result = response.json()
print(f"Flow created successfully with ID: {result.get('id')}")
return result
except requests.exceptions.HTTPError as http_err:
print(f"HTTP error occurred: {http_err}")
print(f"Response content: {response.text}")
raise
except Exception as err:
print(f"Other error occurred: {err}")
raise
# Execute deployment
created_flow = deploy_flow(flow_payload)
Complete Working Example
The following script combines all steps into a single executable module. It handles authentication, flow construction, and deployment.
import requests
import os
import sys
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Configuration
CLIENT_ID = os.getenv("CXONE_CLIENT_ID")
CLIENT_SECRET = os.getenv("CXONE_CLIENT_SECRET")
TENANT_ID = os.getenv("CXONE_TENANT_ID")
PREMIUM_QUEUE_ID = os.getenv("PREMIUM_QUEUE_ID")
STANDARD_QUEUE_ID = os.getenv("STANDARD_QUEUE_ID")
# Validate required variables
if not all([CLIENT_ID, CLIENT_SECRET, TENANT_ID, PREMIUM_QUEUE_ID, STANDARD_QUEUE_ID]):
print("Error: Missing required environment variables.")
sys.exit(1)
def get_access_token() -> str:
url = f"https://{TENANT_ID}.api.nicecxone.com/oauth/token"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
}
response = requests.post(url, headers=headers, data=data)
response.raise_for_status()
return response.json()["access_token"]
def build_flow_json() -> dict:
assign_node = {
"id": "assign_tier",
"type": "ASSIGN",
"properties": {
"variableName": "tier_value",
"expression": {
"type": "FUNCTION",
"functionName": "ifThenElse",
"arguments": [
{
"type": "BOOLEAN",
"value": {
"type": "FUNCTION",
"functionName": "hasAttribute",
"arguments": [
{"type": "STRING", "value": "customer_tier"}
]
}
},
{"type": "ATTRIBUTE", "attributeName": "customer_tier"},
{"type": "STRING", "value": "standard"}
]
}
}
}
if_node = {
"id": "check_tier",
"type": "IF",
"properties": {
"condition": {
"type": "FUNCTION",
"functionName": "equals",
"arguments": [
{"type": "VARIABLE", "variableName": "tier_value"},
{"type": "STRING", "value": "premium"}
]
},
"trueBranch": "route_premium",
"falseBranch": "route_standard"
}
}
route_premium = {
"id": "route_premium",
"type": "ROUTE",
"properties": {
"queueId": PREMIUM_QUEUE_ID,
"timeoutSeconds": 60
}
}
route_standard = {
"id": "route_standard",
"type": "ROUTE",
"properties": {
"queueId": STANDARD_QUEUE_ID,
"timeoutSeconds": 60
}
}
flow_data = {
"name": "Tiered Routing Logic",
"description": "Routes calls based on customer tier attribute.",
"version": 1,
"nodes": [
assign_node,
if_node,
route_premium,
route_standard
],
"startNodeId": assign_node["id"]
}
return flow_data
def deploy_flow(flow_data: dict, token: str) -> dict:
url = f"https://{TENANT_ID}.api.nicecxone.com/api/v2/studio/flows"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=flow_data)
response.raise_for_status()
return response.json()
if __name__ == "__main__":
try:
print("Obtaining access token...")
token = get_access_token()
print("Building flow JSON...")
flow_json = build_flow_json()
print("Deploying flow...")
result = deploy_flow(flow_json, token)
print(f"Success! Flow ID: {result['id']}")
print(f"Flow URL: https://{TENANT_ID}.nicecxone.com/studio/flows/{result['id']}")
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e}")
print(f"Response: {e.response.text}")
except Exception as e:
print(f"Error: {e}")
Common Errors & Debugging
Error: 400 Bad Request - Invalid Node ID Reference
What causes it:
The IF action references trueBranch or falseBranch IDs that do not exist in the nodes array. Studio validates the graph integrity upon submission. If check_tier points to route_premium but route_premium is missing from the nodes list, the API returns a 400 error.
How to fix it:
Ensure every ID referenced in trueBranch or falseBranch matches the id field of a node in the nodes array.
# Incorrect: Reference mismatch
"trueBranch": "route_prem", # Typo in ID
# Correct: Match the node ID exactly
"trueBranch": "route_premium"
Error: 400 Bad Request - Invalid Expression Type
What causes it:
The expression object in the ASSIGN action has incorrect structure. For example, using "value": "premium" instead of {"type": "STRING", "value": "premium"}. Studio strictly enforces the schema for expressions.
How to fix it:
Verify that all literals in expressions are wrapped in their type object (STRING, NUMBER, BOOLEAN). Variables must use {"type": "VARIABLE", "variableName": "name"}.
# Incorrect
"arguments": [
{"type": "VARIABLE", "variableName": "tier_value"},
"premium" # Missing type wrapper
]
# Correct
"arguments": [
{"type": "VARIABLE", "variableName": "tier_value"},
{"type": "STRING", "value": "premium"}
]
Error: 403 Forbidden - Insufficient Scopes
What causes it:
The OAuth token does not include the studio:write scope.
How to fix it:
Update the OAuth client configuration in the CXone Admin Portal to include studio:read and studio:write scopes. Re-authenticate to get a new token with the updated permissions.