Resolving INVALID_FUNCTION Errors in CXone Studio Snippets with GetRESTProxy()
What You Will Build
- You will build a JavaScript Studio Snippet that successfully executes an outbound HTTP request to a third-party API or internal service using the
GetRESTProxy()function. - You will use the NICE CXone Studio API surface, specifically the
GetRESTProxymethod available in the JavaScript execution context of Studio Snippets. - You will write code in JavaScript (ES6+) that is compatible with the CXone Studio runtime environment.
Prerequisites
- Studio Access: You must have access to a NICE CXone Studio instance with permissions to create and test Snippets.
- JavaScript Knowledge: Familiarity with asynchronous JavaScript, Promises, and the
async/awaitsyntax. - Target API: A valid HTTP endpoint to test against (e.g.,
https://jsonplaceholder.typicode.com/posts/1) or a mock service. - Snippet Configuration: Understanding that Studio Snippets execute in a sandboxed Node-like environment that does not support standard
fetchorhttpmodules. You must use the providedGetRESTProxyhelper.
Authentication Setup
Studio Snippets do not require external OAuth authentication for the snippet execution itself because they run within the authenticated context of the IVR session. However, the GetRESTProxy function allows you to inject headers. If your target API requires authentication, you must handle that within the request headers constructed in your snippet.
There is no code-based “login” for the snippet. The authentication is implicit via the Studio runtime. The critical setup is ensuring the snippet is attached to a Studio Flow and that the Flow is published.
Implementation
Step 1: Understanding the GetRESTProxy() Signature
The INVALID_FUNCTION error typically occurs because developers attempt to call GetRESTProxy with incorrect argument structures or attempt to use standard JavaScript networking libraries that are not available in the Studio sandbox.
The correct signature for GetRESTProxy in CXone Studio is:
GetRESTProxy(method, url, headers, body)
- method: A string representing the HTTP method (e.g., “GET”, “POST”, “PUT”, “DELETE”).
- url: A string containing the full URL of the target resource.
- headers: An object (dictionary) containing HTTP headers. If no headers are needed, pass an empty object
{}. - body: A string or object containing the request payload. For GET requests, this is usually
nullor an empty string. For POST/PUT, this should be a JSON stringified object or raw text.
Common Mistake: Passing a configuration object instead of distinct arguments.
Incorrect: GetRESTProxy({ method: 'GET', url: '...' })
Correct: GetRESTProxy('GET', '...', {}, null)
Step 2: Constructing the Request
Let us build a snippet that fetches data from a public JSON API. We will use async/await to handle the promise returned by GetRESTProxy.
First, define the inputs. Studio Snippets allow you to define input variables. For this example, we will assume a single input variable named targetId of type String.
// Define the function to handle the REST call
async function fetchUserData(userId) {
// Define the target URL
const baseUrl = "https://jsonplaceholder.typicode.com/users";
const targetUrl = `${baseUrl}/${userId}`;
// Define headers. Always include Content-Type for POST/PUT.
// For GET, it is optional but good practice to specify Accept.
const headers = {
"Content-Type": "application/json",
"Accept": "application/json"
};
// No body needed for GET
const requestBody = null;
try {
// Call GetRESTProxy
// Note: GetRESTProxy returns a Promise that resolves to an object
const response = await GetRESTProxy("GET", targetUrl, headers, requestBody);
// Log the response for debugging in Studio Logs
console.log("Response Status: " + response.status);
console.log("Response Body: " + response.body);
return response;
} catch (error) {
// Handle errors specifically
console.error("Error in fetchUserData: " + error.message);
throw error;
}
}
// Main execution block
async function main() {
// Retrieve input from the Studio Flow
// Input variables are accessed via the 'inputs' object provided by the runtime
const userId = inputs.targetId;
if (!userId) {
throw new Error("Missing input: targetId");
}
const result = await fetchUserData(userId);
// Return the result to the Studio Flow
// The return value becomes the output of the snippet
return {
status: result.status,
data: result.body
};
}
// Execute the main function
main().catch(err => {
console.error("Snippet failed: " + err);
throw err;
});
Step 3: Handling POST Requests with JSON Bodies
The INVALID_FUNCTION error can also stem from improper serialization of the request body. GetRESTProxy expects the body to be a string if it is JSON. You must explicitly call JSON.stringify().
Here is a snippet that sends a POST request to record an event.
async function postEvent(eventName, eventData) {
const targetUrl = "https://api.example.com/events";
const headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY_HERE" // Example header
};
// Construct the payload object
const payload = {
event_name: eventName,
data: eventData,
timestamp: new Date().toISOString()
};
// CRITICAL: Serialize the object to a JSON string
const requestBody = JSON.stringify(payload);
try {
const response = await GetRESTProxy("POST", targetUrl, headers, requestBody);
// Check for HTTP errors manually if needed,
// though GetRESTProxy often throws on network failure.
if (response.status >= 400) {
throw new Error(`HTTP Error: ${response.status} - ${response.body}`);
}
return response;
} catch (error) {
console.error("POST failed: " + error.message);
throw error;
}
}
async function main() {
const eventName = inputs.eventName || "default_event";
const eventData = inputs.eventData || {};
const result = await postEvent(eventName, eventData);
return {
success: true,
httpStatus: result.status,
responseBody: result.body
};
}
main().catch(err => {
console.error("Snippet execution error: " + err);
throw err;
});
Step 4: Debugging INVALID_FUNCTION Specifically
If you still receive INVALID_FUNCTION, verify the following:
- Spelling: Ensure the function name is exactly
GetRESTProxy. It is case-sensitive.GetRestProxyorgetrestproxywill fail. - Argument Count: Ensure you are passing exactly four arguments.
- Argument Types:
- Argument 1: String
- Argument 2: String
- Argument 3: Object (Dictionary)
- Argument 4: String or Null
If you pass undefined for headers, it may cause issues. Always pass an empty object {} if no headers are required.
// Safe pattern for optional headers
const customHeaders = inputs.customHeaders ? JSON.parse(inputs.customHeaders) : {};
const headers = {
"Content-Type": "application/json",
...customHeaders
};
// Safe pattern for optional body
const body = inputs.payload ? inputs.payload : null;
const response = await GetRESTProxy("POST", "https://example.com/api", headers, body);
Complete Working Example
This is a complete, copy-pasteable Studio Snippet JavaScript code block. It performs a GET request to a public API, parses the response, and returns structured data. It includes robust error handling and logging.
Snippet Inputs:
targetUrl(String): The URL to fetch.
Snippet Outputs:
statusCode(Number): The HTTP status code.responseBody(String): The raw response body.errorMessage(String): Error message if failure occurs, empty string if success.
/**
* CXone Studio Snippet: Secure REST Proxy Caller
*
* Description:
* Executes an HTTP request using the native GetRESTProxy function.
* Handles JSON parsing and error states gracefully.
*
* Inputs:
* - targetUrl (String): The full URL to request.
* - httpMethod (String, Optional): HTTP method. Defaults to GET.
* - headers (String, Optional): JSON string of headers.
* - body (String, Optional): Request body string.
*/
async function executeRestCall() {
// 1. Extract Inputs
const targetUrl = inputs.targetUrl;
let httpMethod = inputs.httpMethod || "GET";
let headersObj = {};
let body = null;
// 2. Validate Input
if (!targetUrl || typeof targetUrl !== "string") {
throw new Error("Input 'targetUrl' is required and must be a string.");
}
// 3. Process Optional Headers
if (inputs.headers && inputs.headers !== "") {
try {
headersObj = JSON.parse(inputs.headers);
} catch (e) {
throw new Error("Invalid JSON in 'headers' input: " + e.message);
}
}
// Ensure Content-Type is set if body exists and not already defined
if (inputs.body && inputs.body !== "") {
body = inputs.body;
if (!headersObj["Content-Type"]) {
headersObj["Content-Type"] = "application/json";
}
}
// 4. Execute GetRESTProxy
// Signature: GetRESTProxy(method, url, headers, body)
let response;
try {
response = await GetRESTProxy(httpMethod.toUpperCase(), targetUrl, headersObj, body);
} catch (networkError) {
// This catches network timeouts, DNS failures, or malformed function calls
throw new Error("Network or Function Error: " + networkError.message);
}
// 5. Handle HTTP Status Codes
// GetRESTProxy does not throw on 4xx/5xx, it returns the response.
// We must check the status manually.
if (response.status >= 400) {
throw new Error(`HTTP Error ${response.status}: ${response.body}`);
}
// 6. Return Structured Output
return {
statusCode: response.status,
responseBody: response.body || "",
errorMessage: ""
};
}
// Main Execution Wrapper
async function main() {
try {
const result = await executeRestCall();
return result;
} catch (error) {
// Return error details in the output structure for the Flow to handle
return {
statusCode: 0,
responseBody: "",
errorMessage: error.message
};
}
}
// Invoke Main
main().catch(err => {
// Final fallback error handler
console.error("Uncaught snippet error: " + err);
throw err;
});
Common Errors & Debugging
Error: INVALID_FUNCTION
What causes it:
- The function name is misspelled (e.g.,
GetRestProxyinstead ofGetRESTProxy). - The number of arguments passed is incorrect.
- One of the arguments is of the wrong type (e.g., passing a Number instead of a String for the URL).
How to fix it:
Verify the call matches this pattern exactly:
await GetRESTProxy("GET", "https://example.com", {}, null);
Ensure headers is an object {} and not null or undefined.
Error: Network Timeout or Connection Refused
What causes it:
- The target URL is unreachable from the CXone data center.
- The URL uses
localhostor a private IP address (CXone runs in the cloud and cannot reach local networks). - The target server is down or blocking the request.
How to fix it:
- Ensure the target URL is publicly accessible.
- Check that the target server allows requests from the CXone IP ranges (check NICE CXone IP documentation).
- Use
console.logto verify the URL being constructed is correct.
Error: JSON Parse Error in Response
What causes it:
- The target API returns HTML (e.g., an error page) instead of JSON.
- The response body is empty, and your code attempts to
JSON.parse("").
How to fix it:
Always check if the response body exists and is valid JSON before parsing.
let parsedData = {};
if (response.body) {
try {
parsedData = JSON.parse(response.body);
} catch (e) {
console.warn("Response body is not valid JSON: " + response.body);
parsedData = { raw: response.body };
}
}
Error: 401 Unauthorized or 403 Forbidden
What causes it:
- Missing or incorrect API keys in headers.
- The target API requires specific OAuth scopes.
How to fix it:
Add the necessary authentication headers in the headers object passed to GetRESTProxy.
const headers = {
"Authorization": "Bearer " + inputs.apiToken,
"Content-Type": "application/json"
};