Resolving INVALID_FUNCTION Errors in CXone Studio Snippets with GetRESTProxy()

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 GetRESTProxy method 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/await syntax.
  • 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 fetch or http modules. You must use the provided GetRESTProxy helper.

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 null or 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:

  1. Spelling: Ensure the function name is exactly GetRESTProxy. It is case-sensitive. GetRestProxy or getrestproxy will fail.
  2. Argument Count: Ensure you are passing exactly four arguments.
  3. 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., GetRestProxy instead of GetRESTProxy).
  • 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 localhost or 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.log to 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"
};

Official References