Implementing Branching Logic with ASSIGN and IF in NICE CXone Studio

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 requests library 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. The ifThenElse function 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, equals compares the variable tier_value against 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.

Official References