How to Automate CXone Studio Flow Branching with ASSIGN and IF Actions via API
What You Will Build
- You will programmatically create a NICE CXone Studio flow that uses ASSIGN actions to set variables and IF actions to route execution based on those variables.
- This tutorial uses the NICE CXone Studio API (
/api/v2/studio/flows) and the CXone SDK for JavaScript/Node.js. - The code is written in JavaScript (ES Modules) using the
axioslibrary for HTTP requests, demonstrating the exact JSON structure required for complex branching logic.
Prerequisites
- OAuth Client: A CXone Application with the
studio:flow:writeandstudio:flow:readscopes. - SDK Version: NICE CXone Node.js SDK v2+ or direct REST API access.
- Runtime: Node.js v18+ or later.
- Dependencies:
axiosfor HTTP requests,dotenvfor environment variable management. - Studio Context: An existing Studio Flow ID or the intent to create a new one.
Authentication Setup
Before interacting with the Studio API, you must obtain a valid OAuth 2.0 access token. The Studio API endpoints are protected and require a token with the appropriate scopes. The following code demonstrates a robust authentication helper that handles token retrieval.
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const { CXONE_BASE_URL, CXONE_CLIENT_ID, CXONE_CLIENT_SECRET } = process.env;
/**
* Retrieves an OAuth2 access token from NICE CXone.
* @returns {Promise<string>} The access token.
*/
async function getAccessToken() {
const authUrl = `${CXONE_BASE_URL}/oauth/token`;
const payload = new URLSearchParams();
payload.append('grant_type', 'client_credentials');
payload.append('client_id', CXONE_CLIENT_ID);
payload.append('client_secret', CXONE_CLIENT_SECRET);
try {
const response = await axios.post(authUrl, payload, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
if (response.status !== 200) {
throw new Error(`Authentication failed with status ${response.status}`);
}
return response.data.access_token;
} catch (error) {
console.error('Failed to retrieve access token:', error.message);
throw error;
}
}
You must store your credentials in a .env file:
CXONE_BASE_URL=https://api.euw1.nice.incontact.com
CXONE_CLIENT_ID=your_client_id_here
CXONE_CLIENT_SECRET=your_client_secret_here
Implementation
Step 1: Define the Flow Skeleton and ASSIGN Action
The first step in building branching logic is to define the flow structure. In CXone Studio, a flow is a directed acyclic graph (DAG) of nodes. We will create a simple flow that starts, assigns a value to a variable, and then branches based on that value.
The ASSIGN action is represented by a node of type assign. It requires a assignments array where each object specifies a variable name and a value. The value can be a literal or an expression.
/**
* Creates a basic flow skeleton with an ASSIGN node.
* @param {string} token - The OAuth access token.
* @returns {Promise<object>} The created flow object.
*/
async function createBaseFlow(token) {
const flowName = `DevOps Branching Example - ${Date.now()}`;
const endpoint = `${CXONE_BASE_URL}/api/v2/studio/flows`;
const flowPayload = {
name: flowName,
description: 'Automated flow demonstrating ASSIGN and IF logic.',
type: 'IVR', // Can be 'IVR', 'SMS', 'Email', etc.
nodes: [
{
id: 'start',
type: 'start',
label: 'Start',
next: 'assign_node', // Directs flow to the ASSIGN node
properties: {}
},
{
id: 'assign_node',
type: 'assign',
label: 'Set Customer Tier',
next: 'if_node', // Directs flow to the IF node
properties: {
assignments: [
{
variable: 'customerTier',
value: 'Premium', // Literal string assignment
type: 'string'
}
]
}
}
],
settings: {
defaultLanguage: 'en-US'
}
};
try {
const response = await axios.post(endpoint, flowPayload, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
console.log('Base flow created successfully:', response.data.id);
return response.data;
} catch (error) {
if (error.response) {
console.error('API Error:', error.response.data);
} else {
console.error('Request Error:', error.message);
}
throw error;
}
}
Key Parameter Explanation:
id: A unique identifier for the node within this flow. It must be unique across all nodes in thenodesarray.type: For assignment, this is strictlyassign.next: The ID of the next node to execute. In branching scenarios, this is often determined by the IF node, but linear flows use a static ID.assignments: An array of objects. Each object must havevariable(the name of the Studio variable),value(the literal or expression), andtype(e.g.,string,number,boolean).
Step 2: Implement Branching Logic with IF Actions
The IF action is represented by a node of type if. Unlike linear nodes, an IF node has multiple outgoing paths: true and false (and optionally default). You must define the condition using the condition property.
The condition is a string expression that evaluates to a boolean. CXone Studio supports a specific expression language similar to JavaScript but with its own syntax for accessing variables and functions.
/**
* Updates the flow to include an IF node that branches based on the assigned variable.
* @param {string} token - The OAuth access token.
* @param {string} flowId - The ID of the flow to update.
* @returns {Promise<object>} The updated flow object.
*/
async function addBranchingLogic(token, flowId) {
const endpoint = `${CXONE_BASE_URL}/api/v2/studio/flows/${flowId}`;
// First, fetch the existing flow to ensure we do not overwrite other changes
const fetchResponse = await axios.get(endpoint, {
headers: { 'Authorization': `Bearer ${token}` }
});
const existingFlow = fetchResponse.data;
const nodes = existingFlow.nodes;
// Add the IF node
const ifNode = {
id: 'if_node',
type: 'if',
label: 'Check if Premium',
// The condition checks if the variable 'customerTier' equals 'Premium'
condition: "variable('customerTier') == 'Premium'",
// Define the next node for TRUE and FALSE branches
true: 'premium_path',
false: 'standard_path',
properties: {}
};
// Add endpoint nodes for each branch to visualize the result
const premiumNode = {
id: 'premium_path',
type: 'say', // Using 'say' as a placeholder for an action
label: 'Premium Greeting',
next: 'end',
properties: {
text: 'Welcome, valued Premium member.'
}
};
const standardNode = {
id: 'standard_path',
type: 'say',
label: 'Standard Greeting',
next: 'end',
properties: {
text: 'Welcome to our service.'
}
};
const endNode = {
id: 'end',
type: 'end',
label: 'End Flow',
properties: {}
};
// Update the nodes array in the existing flow
// We remove the old 'assign_node' next pointer and add the new nodes
const updatedNodes = nodes.filter(node => node.id !== 'if_node' && node.id !== 'premium_path' && node.id !== 'standard_path' && node.id !== 'end');
// Ensure the assign_node points to the if_node
const assignNodeIndex = updatedNodes.findIndex(n => n.id === 'assign_node');
if (assignNodeIndex !== -1) {
updatedNodes[assignNodeIndex].next = 'if_node';
}
// Append new nodes
updatedNodes.push(ifNode, premiumNode, standardNode, endNode);
const updatedFlowPayload = {
...existingFlow,
nodes: updatedNodes
};
try {
const response = await axios.put(endpoint, updatedFlowPayload, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
console.log('Branching logic added successfully.');
return response.data;
} catch (error) {
if (error.response) {
console.error('API Error:', error.response.data);
} else {
console.error('Request Error:', error.message);
}
throw error;
}
}
Key Parameter Explanation:
condition: This is a string containing the Studio Expression Language. To access a variable, usevariable('variableName'). To compare, use standard operators like==,!=,>,<.true: The ID of the node to execute if the condition evaluates to true.false: The ID of the node to execute if the condition evaluates to false.default: Optional. Used if the condition evaluates to null or undefined. If omitted, the flow may throw an error or stop depending on global settings.
Step 3: Handling Complex Expressions and Data Types
Often, you need to branch based on numeric values, dates, or external data. The ASSIGN action can store numbers and booleans. The IF condition must match the data type.
Here is how you modify the ASSIGN action to set a numeric variable and branch based on a threshold.
/**
* Demonstrates assigning a numeric value and branching on a numeric comparison.
* @param {string} token - The OAuth access token.
* @param {string} flowId - The ID of the flow to update.
*/
async function updateWithNumericBranching(token, flowId) {
const endpoint = `${CXONE_BASE_URL}/api/v2/studio/flows/${flowId}`;
const fetchResponse = await axios.get(endpoint, {
headers: { 'Authorization': `Bearer ${token}` }
});
let nodes = fetchResponse.data.nodes;
// Update the existing assign node to set a numeric variable
const assignNodeIndex = nodes.findIndex(n => n.id === 'assign_node');
if (assignNodeIndex !== -1) {
nodes[assignNodeIndex].properties.assignments = [
{
variable: 'accountBalance',
value: '1500', // Stored as string in API, but interpreted as number in expression
type: 'number'
}
];
}
// Find the IF node and update its condition
const ifNodeIndex = nodes.findIndex(n => n.id === 'if_node');
if (ifNodeIndex !== -1) {
// Condition: Check if accountBalance is greater than 1000
nodes[ifNodeIndex].condition = "number(variable('accountBalance')) > 1000";
nodes[ifNodeIndex].label = 'Check Balance Threshold';
// Update the labels of the next nodes to reflect the new logic
const premiumNodeIndex = nodes.findIndex(n => n.id === 'premium_path');
if (premiumNodeIndex !== -1) {
nodes[premiumNodeIndex].label = 'High Balance Path';
nodes[premiumNodeIndex].properties.text = 'Your balance exceeds the threshold.';
}
const standardNodeIndex = nodes.findIndex(n => n.id === 'standard_path');
if (standardNodeIndex !== -1) {
nodes[standardNodeIndex].label = 'Low Balance Path';
nodes[standardNodeIndex].properties.text = 'Your balance is within standard limits.';
}
}
try {
const response = await axios.put(endpoint, {
...fetchResponse.data,
nodes: nodes
}, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
console.log('Numeric branching logic updated.');
} catch (error) {
console.error('Failed to update flow:', error.response?.data || error.message);
}
}
Critical Note on Data Types:
In CXone Studio expressions, variables are often retrieved as strings. To perform numeric comparisons, you must explicitly cast the variable using number(variable('name')). Similarly, use boolean(variable('name')) for boolean logic. Failing to cast can lead to string comparisons (e.g., "1500" > "1000" is true, but "999" > "1000" is false in string comparison, which is correct, but "9" > "10" is true in string comparison, which is incorrect for numbers).
Complete Working Example
The following script combines authentication, flow creation, and branching logic implementation into a single runnable module.
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const { CXONE_BASE_URL, CXONE_CLIENT_ID, CXONE_CLIENT_SECRET } = process.env;
async function getAccessToken() {
const authUrl = `${CXONE_BASE_URL}/oauth/token`;
const payload = new URLSearchParams();
payload.append('grant_type', 'client_credentials');
payload.append('client_id', CXONE_CLIENT_ID);
payload.append('client_secret', CXONE_CLIENT_SECRET);
try {
const response = await axios.post(authUrl, payload, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
return response.data.access_token;
} catch (error) {
throw new Error(`Authentication failed: ${error.message}`);
}
}
async function createAndConfigureBranchingFlow() {
let token;
try {
token = await getAccessToken();
console.log('Authenticated successfully.');
} catch (error) {
console.error('Authentication failed. Check your credentials.');
return;
}
const flowName = `DevOps Branching Example - ${Date.now()}`;
const endpoint = `${CXONE_BASE_URL}/api/v2/studio/flows`;
const initialFlowPayload = {
name: flowName,
description: 'Automated flow demonstrating ASSIGN and IF logic.',
type: 'IVR',
nodes: [
{
id: 'start',
type: 'start',
label: 'Start',
next: 'assign_node',
properties: {}
},
{
id: 'assign_node',
type: 'assign',
label: 'Set Account Balance',
next: 'if_node',
properties: {
assignments: [
{
variable: 'accountBalance',
value: '1500',
type: 'number'
}
]
}
},
{
id: 'if_node',
type: 'if',
label: 'Check Balance Threshold',
condition: "number(variable('accountBalance')) > 1000",
true: 'high_balance_path',
false: 'low_balance_path',
properties: {}
},
{
id: 'high_balance_path',
type: 'say',
label: 'High Balance Path',
next: 'end',
properties: {
text: 'Your balance exceeds the threshold.'
}
},
{
id: 'low_balance_path',
type: 'say',
label: 'Low Balance Path',
next: 'end',
properties: {
text: 'Your balance is within standard limits.'
}
},
{
id: 'end',
type: 'end',
label: 'End Flow',
properties: {}
}
],
settings: {
defaultLanguage: 'en-US'
}
};
try {
const createResponse = await axios.post(endpoint, initialFlowPayload, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
console.log(`Flow created successfully with ID: ${createResponse.data.id}`);
console.log(`Flow Name: ${createResponse.data.name}`);
// Optional: Verify the flow structure
const verifyResponse = await axios.get(`${endpoint}/${createResponse.data.id}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
console.log('Flow verification complete. Nodes count:', verifyResponse.data.nodes.length);
} catch (error) {
if (error.response) {
console.error('API Error:', error.response.status, error.response.data);
} else {
console.error('Request Error:', error.message);
}
}
}
createAndConfigureBranchingFlow();
Common Errors & Debugging
Error: 400 Bad Request - Invalid Node Structure
Cause: The Studio API is strict about node connectivity. Every node (except end) must have a next property or valid branching paths (true/false). If an IF node references a non-existent ID in its true or false properties, the API rejects the payload.
Fix: Ensure all referenced IDs exist in the nodes array.
// Incorrect: 'non_existent_node' does not exist
{
id: 'if_node',
type: 'if',
condition: "true",
true: 'non_existent_node',
false: 'end'
}
// Correct: 'premium_path' exists in the nodes array
{
id: 'if_node',
type: 'if',
condition: "true",
true: 'premium_path',
false: 'end'
}
Error: 400 Bad Request - Expression Evaluation Error
Cause: The condition string in the IF node contains invalid syntax or references a variable that has not been assigned or does not exist in the context.
Fix: Validate the expression syntax. Use variable('name') to access variables. Ensure the variable is assigned before the IF node is reached in the flow execution path.
// Incorrect: Direct variable reference without function
condition: "customerTier == 'Premium'"
// Correct: Using variable() function
condition: "variable('customerTier') == 'Premium'"
Error: 401 Unauthorized
Cause: The access token is expired or missing the studio:flow:write scope.
Fix: Regenerate the token using the getAccessToken() function. Ensure your CXone Application has the correct scopes assigned in the Admin Portal.
Error: 409 Conflict - Flow Already Exists
Cause: Attempting to create a flow with a name that already exists in the same environment.
Fix: Use a unique name, such as appending a timestamp, or update an existing flow using the PUT endpoint instead of POST.