NICE CXone Studio: Execute REST API Calls and Parse JSON in Snippets
What You Will Build
- A NICE CXone Studio Snippet that performs an authenticated HTTP GET request to an external REST API, parses the JSON response, and stores specific values into CXone Session Variables for use in downstream flows.
- This tutorial uses the NICE CXone Studio Snippet runtime environment (JavaScript/Node.js context).
- The programming language covered is JavaScript (ES6+) executed within the CXone Snippet engine.
Prerequisites
- NICE CXone Organization Access: You must have permissions to edit Studio Flows and create Snippets.
- Studio Snippet License: Your organization must have the Studio Snippet capability enabled.
- External API Endpoint: A valid HTTPS endpoint that returns JSON. For this tutorial, we will use the public
https://jsonplaceholder.typicode.com/users/1endpoint as a safe, rate-limit-free target. - Dependencies: No external
npmpackages are installed in the Snippet runtime. You rely on the built-infetchAPI (orhttpsmodule iffetchis unavailable in older runtime versions, thoughfetchis standard in current CXone Node.js runtimes).
Authentication Setup
CXone Snippets run in a serverless context. Unlike standard Node.js applications, you do not manage a persistent OAuth token cache in memory across invocations. Each snippet execution is stateless.
If your target API requires authentication (e.g., OAuth 2.0), you must handle the token retrieval within the snippet or pass a pre-fetched token via a Session Variable. For this tutorial, we assume a public API. If you need to call a protected API, the pattern is identical, but you would prepend a POST request to your token endpoint to retrieve the access_token before making the primary call.
Critical Note: Never hardcode API keys or secrets in the Snippet code. Use CXone Secrets or pass sensitive data via encrypted Session Variables if your organization supports secret management injection.
Implementation
Step 1: Define the Snippet Input and Output Schema
Before writing code, you must define the contract between the Studio Flow and the Snippet. This ensures type safety and documentation.
In the Studio Snippet editor, configure the following:
Input Parameters:
targetUrl(String): The URL to fetch.timeoutMs(Number): Optional timeout in milliseconds. Default: 5000.
Output Parameters:
statusCode(Number): The HTTP status code returned by the server.responseBody(String): The raw JSON string returned.parsedName(String): Thenamefield extracted from the JSON.parsedEmail(String): Theemailfield extracted from the JSON.errorMessage(String): Empty if success, contains error details if failure.
Step 2: Core Logic — The Fetch and Parse Routine
The core of the snippet is an async function that handles the HTTP request. We use async/await for readability and error handling. We must handle network timeouts, non-2xx status codes, and JSON parsing errors.
/**
* Main entry point for the CXone Snippet.
* @param {object} input - The input object passed from Studio.
* @returns {object} - The output object to be returned to Studio.
*/
async function main(input) {
const { targetUrl, timeoutMs = 5000 } = input;
// Initialize output object with default empty values
const output = {
statusCode: 0,
responseBody: "",
parsedName: "",
parsedEmail: "",
errorMessage: ""
};
try {
// Step 1: Validate Input
if (!targetUrl) {
throw new Error("Input parameter 'targetUrl' is required.");
}
// Step 2: Configure Request with Timeout
// The 'fetch' API in CXone Snippets supports the standard AbortController for timeouts.
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeoutMs);
// Step 3: Execute the HTTP GET Request
const response = await fetch(targetUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
// Add Authorization header here if needed:
// 'Authorization': `Bearer ${input.authToken}`
},
signal: controller.signal
});
clearTimeout(id); // Clear the timeout if the request completes first
// Step 4: Store Status Code
output.statusCode = response.status;
// Step 5: Read Response Body as Text first
const text = await response.text();
output.responseBody = text;
// Step 6: Check for HTTP Errors
if (!response.ok) {
// response.ok is true for 200-299 status codes
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
}
// Step 7: Parse JSON
// We attempt to parse. If it fails, we catch it and populate the error field
// but still return the raw text so the developer can debug.
let jsonData;
try {
jsonData = JSON.parse(text);
} catch (parseError) {
throw new Error(`JSON Parse Error: ${parseError.message}`);
}
// Step 8: Extract Specific Fields (Safe Navigation)
// Using optional chaining (?.) to prevent crashes if fields are missing
output.parsedName = jsonData?.name || "";
output.parsedEmail = jsonData?.email || "";
} catch (error) {
// Step 9: Error Handling
// Capture any network error, timeout, or parsing error
output.errorMessage = error.message || "Unknown error occurred";
// Log to CXone Snippet logs for debugging
console.error("Snippet Error:", error);
}
return output;
}
Step 3: Processing Results and Edge Cases
In a production environment, you must account for malformed JSON and missing keys. The code above uses optional chaining (?.) to safely access name and email. If the JSON structure changes (e.g., the API returns { "user": { "name": "..." } }), your snippet will return empty strings for parsedName rather than crashing.
Handling Large Responses:
CXone Snippets have memory limits. If the JSON response is extremely large (e.g., >5MB), parsing it into memory may cause an Out-Of-Memory (OOM) error.
- Best Practice: If you only need one field, consider using a streaming parser or filtering the data on the server side before sending it to the snippet. However, for standard CRUD operations and status checks,
JSON.parseis sufficient.
Retrying on 429 (Too Many Requests):
If your target API returns a 429, you should implement a retry loop. Here is how you modify Step 2 to include retries:
const MAX_RETRIES = 3;
let attempt = 0;
let lastError = null;
while (attempt < MAX_RETRIES) {
try {
const response = await fetch(targetUrl, { signal: controller.signal });
if (response.status === 429) {
// Check Retry-After header if present
const retryAfter = response.headers.get('Retry-After');
const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : 1000 * (attempt + 1);
console.log(`Rate limited. Waiting ${waitTime}ms before retry...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
attempt++;
continue; // Retry the loop
}
// If not 429, proceed with normal processing (break out of loop)
// ... [Rest of processing logic] ...
break;
} catch (e) {
lastError = e;
attempt++;
if (attempt >= MAX_RETRIES) throw e;
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
Complete Working Example
This is the full, copy-pasteable code for the Snippet. It includes input validation, timeout handling, JSON parsing, and structured error reporting.
Snippet Name: FetchAndParseJSON
Runtime: Node.js 14+ (Standard CXone Runtime)
/**
* CXone Studio Snippet: FetchAndParseJSON
*
* Description:
* Performs an HTTP GET request to a specified URL, parses the JSON response,
* and extracts specific fields. Handles timeouts and HTTP errors gracefully.
*
* Inputs:
* - targetUrl (String): The endpoint to call.
* - timeoutMs (Number): Timeout in milliseconds (default 5000).
*
* Outputs:
* - statusCode (Number): HTTP Status Code.
* - responseBody (String): Raw JSON response.
* - parsedName (String): Value of 'name' key from JSON.
* - parsedEmail (String): Value of 'email' key from JSON.
* - errorMessage (String): Error message if failed, empty string if success.
*/
async function main(input) {
// 1. Destructure and Validate Inputs
const { targetUrl, timeoutMs } = input;
const TIMEOUT_LIMIT = timeoutMs || 5000;
// Default Output Structure
const output = {
statusCode: 0,
responseBody: "",
parsedName: "",
parsedEmail: "",
errorMessage: ""
};
// Validation
if (typeof targetUrl !== 'string' || targetUrl.trim() === '') {
output.errorMessage = "Invalid input: targetUrl must be a non-empty string.";
console.error(output.errorMessage);
return output;
}
try {
// 2. Setup AbortController for Timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_LIMIT);
// 3. Execute Fetch
console.log(`Attempting to fetch: ${targetUrl}`);
const response = await fetch(targetUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
'User-Agent': 'CXoneStudioSnippet/1.0'
},
signal: controller.signal
});
clearTimeout(timeoutId);
// 4. Capture Status
output.statusCode = response.status;
// 5. Read Body
const rawText = await response.text();
output.responseBody = rawText;
// 6. Handle HTTP Errors
if (!response.ok) {
// response.ok is false for 4xx and 5xx
throw new Error(`HTTP Request Failed with Status ${response.status}: ${response.statusText}`);
}
// 7. Parse JSON
let data;
try {
data = JSON.parse(rawText);
} catch (jsonErr) {
throw new Error(`Failed to parse JSON response. Error: ${jsonErr.message}`);
}
// 8. Extract Fields (Safe Navigation)
// If the API returns an object, extract fields.
// If it returns an array, you might want data[0].name, etc.
if (data && typeof data === 'object') {
output.parsedName = data.name || "";
output.parsedEmail = data.email || "";
} else {
output.errorMessage = "Response JSON was not an object.";
}
} catch (error) {
// 9. Centralized Error Handling
// Distinguish between AbortError (Timeout) and other errors
if (error.name === 'AbortError') {
output.errorMessage = `Request timed out after ${TIMEOUT_LIMIT}ms.`;
} else {
output.errorMessage = error.message || "An unexpected error occurred.";
}
// Log stack trace for CXone Admin debugging
console.error("Snippet Execution Error:", error.stack || error);
}
// 10. Return Output
return output;
}
Common Errors & Debugging
Error: TypeError: fetch is not a function
Cause: You are running this snippet in an older CXone runtime environment that does not support the global fetch API.
Fix: Use the native https module instead.
const https = require('https');
const http = require('http');
function makeRequest(url, timeout) {
return new Promise((resolve, reject) => {
const client = url.startsWith('https') ? https : http;
const req = client.get(url, { timeout: timeout }, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
resolve({
status: res.statusCode,
body: data
});
});
});
req.on('error', (err) => reject(err));
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timed out'));
});
});
}
Error: SyntaxError: Unexpected token < in JSON at position 0
Cause: The API returned HTML (e.g., an error page, login redirect, or Cloudflare challenge) instead of JSON.
Fix: Check the responseBody output variable in Studio. If it starts with <!DOCTYPE html>, your request is likely being blocked by a firewall, requires authentication, or the URL is incorrect. Ensure the Accept: application/json header is sent.
Error: Maximum execution time exceeded
Cause: The snippet ran longer than the CXone platform limit (typically 10-30 seconds depending on configuration).
Fix: Reduce the timeoutMs parameter. Ensure the target API is responsive. Avoid heavy processing or large JSON parsing in the snippet. If the operation is long-running, use a webhook pattern instead of a synchronous snippet.
Error: Access Denied or 403 Forbidden
Cause: The target API requires authentication, or CXone’s outbound IP ranges are blocked by the target firewall.
Fix:
- Check if the API requires an API Key or Bearer Token. Add it to the headers.
- Verify that the target firewall allows traffic from NICE CXone IP ranges. You can find these in the NICE CXone documentation under “IP Addresses and Ports”.