Fixing INVALID_FUNCTION in CXone Studio: Correct Syntax for GetRESTProxy()

Fixing INVALID_FUNCTION in CXone Studio: Correct Syntax for GetRESTProxy()

What You Will Build

  • One sentence: This tutorial demonstrates the correct syntax and initialization pattern for calling external REST APIs within a NICE CXone Studio Snippet.
  • One sentence: This uses the CXone Studio Snippet execution environment and the internal GetRESTProxy helper function.
  • One sentence: The programming language covered is JavaScript (Node.js runtime context).

Prerequisites

  • OAuth Client: A registered OAuth client in CXone with api:read and api:write scopes if accessing protected endpoints, or public endpoints requiring no auth.
  • Studio Access: Access to the CXone Studio workspace to create and test Snippets.
  • Runtime: CXone Studio Snippet Engine (internal Node.js environment).
  • Dependencies: No external npm packages are installed. You must use the global objects provided by the Studio runtime ($, GetRESTProxy, log).

Authentication Setup

CXone Studio Snippets run in a sandboxed environment. Authentication for external APIs depends on the target system. For this tutorial, we assume two scenarios:

  1. Public Endpoint: No authentication required (e.g., https://jsonplaceholder.typicode.com).
  2. Internal CXone API: Uses the session’s implicit context or requires explicit OAuth header injection if calling cross-tenant or specific protected resources.

The GetRESTProxy function does not handle OAuth token generation for you. You must construct the Authorization header manually if the target API requires it.

// Example: Constructing an Authorization Header for a public key-based API
const apiKey = "your_api_key_here";
const headers = {
    "X-API-Key": apiKey,
    "Content-Type": "application/json"
};

For internal CXone APIs, you often do not need to pass a token explicitly if the snippet is executing within a flow context that has permissions, but GetRESTProxy is primarily designed for external calls. If you need to call internal CXone APIs, the $ object methods (e.g., $api.get) are preferred. GetRESTProxy is for when $ does not provide a method.

Implementation

Step 1: Initializing GetRESTProxy Correctly

The most common cause of the INVALID_FUNCTION error is incorrect initialization of the proxy object. Developers often attempt to call GetRESTProxy as a method on the $ object (e.g., $.GetRESTProxy()) or pass arguments incorrectly.

GetRESTProxy is a global function in the Studio Snippet scope. It returns an object with HTTP method verbs (get, post, put, delete).

Incorrect Syntax (Causes INVALID_FUNCTION):

// WRONG: GetRESTProxy is not a method of $
const proxy = $.GetRESTProxy();

// WRONG: Passing URL during initialization is not supported
const proxy = GetRESTProxy("https://api.example.com");

Correct Syntax:

// CORRECT: Call global GetRESTProxy() with no arguments
const proxy = GetRESTProxy();

Once initialized, you chain the HTTP method.

const response = proxy.get("https://jsonplaceholder.typicode.com/users/1");

Step 2: Executing a GET Request

We will build a snippet that fetches a user record from a public API. This demonstrates the basic flow: Initialize, Call, Parse, Handle Errors.

// Initialize the proxy
const proxy = GetRESTProxy();

// Define the URL
const url = "https://jsonplaceholder.typicode.com/users/1";

try {
    // Execute the GET request
    // The method returns an object with status, body, and headers
    const result = proxy.get(url);

    // Check HTTP status code
    if (result.status >= 200 && result.status < 300) {
        // Parse the JSON body
        const data = JSON.parse(result.body);
        
        // Log the result to Studio Logs
        log.info("User Name: " + data.name);
        
        // Set a variable for use in the rest of the flow
        $.set("userName", data.name);
    } else {
        log.error("HTTP Error: " + result.status + " - " + result.body);
    }
} catch (e) {
    // Catch network errors or parsing errors
    log.error("Exception: " + e.message);
}

Expected Response Object Structure:
The proxy.get() call returns a JavaScript object, not a raw HTTP stream.

{
  "status": 200,
  "body": "{\"id\":1,\"name\":\"Leanne Graham\",...}",
  "headers": {
    "content-type": "application/json; charset=utf-8",
    "server": "cloudflare"
  }
}

Step 3: Executing a POST Request with Headers and Body

When sending data, you must pass a configuration object as the second argument to the HTTP method. This object contains headers and body.

Common Mistake: Passing the body as a string without setting the Content-Type header, or passing the body as a JSON object without stringifying it.

const proxy = GetRESTProxy();
const url = "https://httpbin.org/post";

// Data to send
const payload = {
    "message": "Hello from CXone Studio",
    "timestamp": new Date().toISOString()
};

// Define headers
const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json"
};

try {
    // POST requires the body to be a string
    const result = proxy.post(url, {
        headers: headers,
        body: JSON.stringify(payload)
    });

    if (result.status === 200) {
        const responseData = JSON.parse(result.body);
        log.info("Server received: " + JSON.stringify(responseData.json));
        $.set("postSuccess", true);
    } else {
        log.error("POST Failed: " + result.status);
    }
} catch (e) {
    log.error("POST Exception: " + e.message);
}

Step 4: Handling Authentication (Bearer Token)

If the external API requires OAuth2 Bearer tokens, you must inject the token into the headers. Note that CXone Studio does not automatically attach the current user’s CXone token to external GetRESTProxy calls.

const proxy = GetRESTProxy();
const url = "https://api.example.com/secure/data";

// Assume you have retrieved the token previously in the flow
// and stored it in a variable 'externalToken'
const token = $.get("externalToken"); 

if (!token) {
    log.error("Missing external token");
    return;
}

const headers = {
    "Authorization": "Bearer " + token,
    "Content-Type": "application/json"
};

try {
    const result = proxy.get(url, { headers: headers });

    if (result.status === 200) {
        const data = JSON.parse(result.body);
        $.set("secureData", data);
    } else if (result.status === 401) {
        log.error("Authentication failed. Token may be expired.");
    } else {
        log.error("Error: " + result.status);
    }
} catch (e) {
    log.error("Request failed: " + e.message);
}

Complete Working Example

This is a robust, copy-pasteable Snippet script. It handles initialization, GET/POST logic, JSON parsing, and error catching. Replace the URLs and credentials with your target system.

/**
 * CXone Studio Snippet: External API Caller
 * 
 * Usage:
 * 1. Set input variables in the Snippet Configuration:
 *    - targetUrl: The API endpoint
 *    - method: "GET" or "POST"
 *    - apiKey: (Optional) API Key for authentication
 *    - requestBody: (Optional) JSON string for POST
 */

// 1. Initialize GetRESTProxy
// This is the global function, NOT a method of $
const proxy = GetRESTProxy();

// 2. Retrieve Inputs from Flow Context
const targetUrl = $.get("targetUrl");
const httpMethod = $.get("method") || "GET";
const apiKey = $.get("apiKey");
const requestBody = $.get("requestBody");

// 3. Validate Inputs
if (!targetUrl) {
    log.error("Snippet Error: targetUrl is not set.");
    $.set("snippetError", "Missing targetUrl");
    return;
}

// 4. Construct Headers
const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json"
};

// Add API Key if provided
if (apiKey) {
    headers["X-API-Key"] = apiKey;
}

// 5. Execute Request
let result;
try {
    if (httpMethod === "GET") {
        // GET requests usually do not have a body, but headers are optional in the 2nd arg
        result = proxy.get(targetUrl, { headers: headers });
    } else if (httpMethod === "POST") {
        // POST requires a body string
        if (!requestBody) {
            log.error("Snippet Error: requestBody required for POST method.");
            $.set("snippetError", "Missing requestBody for POST");
            return;
        }
        result = proxy.post(targetUrl, { 
            headers: headers, 
            body: requestBody 
        });
    } else {
        log.error("Snippet Error: Unsupported method " + httpMethod);
        $.set("snippetError", "Unsupported HTTP Method");
        return;
    }
} catch (networkError) {
    log.error("Network Error: " + networkError.message);
    $.set("snippetError", "Network Failure");
    $.set("httpStatus", 0);
    return;
}

// 6. Process Response
$.set("httpStatus", result.status);
$.set("responseHeaders", JSON.stringify(result.headers));

if (result.status >= 200 && result.status < 300) {
    try {
        // Parse JSON body
        const jsonData = JSON.parse(result.body);
        $.set("responseBody", jsonData);
        $.set("snippetError", null); // Clear any previous errors
        log.info("Success: " + result.status);
    } catch (parseError) {
        // Body is not valid JSON, return raw string
        $.set("responseBody", result.body);
        log.warn("Response body is not JSON. Returning raw text.");
    }
} else {
    // HTTP Error
    $.set("responseBody", result.body);
    $.set("snippetError", "HTTP " + result.status);
    log.error("API Error: " + result.status + " Body: " + result.body);
}

Common Errors & Debugging

Error: INVALID_FUNCTION

What causes it:
This error occurs when the Studio engine cannot resolve the function call. In the context of GetRESTProxy, this is almost always due to:

  1. Calling $.GetRESTProxy() instead of GetRESTProxy().
  2. Passing arguments to GetRESTProxy() during initialization (e.g., GetRESTProxy(url)).
  3. Typing errors, such as GetRestProxy (lowercase ‘est’).

How to fix it:
Ensure you are calling the global function with no arguments.

// Fix
const proxy = GetRESTProxy();

Error: SyntaxError: Unexpected token o in JSON at position 1

What causes it:
You are trying to parse a JavaScript object as JSON. This happens if you do JSON.parse(proxy.get(url)) incorrectly or if you reuse a parsed object. It also happens if the response body is already an object in some newer runtime versions, though typically proxy returns a string body.

How to fix it:
Check the type of result.body before parsing.

let data;
if (typeof result.body === "string") {
    data = JSON.parse(result.body);
} else {
    data = result.body;
}

Error: TypeError: Cannot read property ‘status’ of undefined

What causes it:
The proxy.get() or proxy.post() call threw an exception (network timeout, DNS failure) and did not return a result object. You are trying to access result.status on an undefined variable because the exception was not caught.

How to fix it:
Always wrap the proxy call in a try/catch block.

let result;
try {
    result = proxy.get(url);
} catch (e) {
    log.error("Request failed: " + e.message);
    return;
}

// Safe to access result.status now
if (result.status === 200) { ... }

Error: 403 Forbidden or 401 Unauthorized

What causes it:
The external API rejected the request. This is often due to missing headers or incorrect authentication tokens.

How to fix it:

  1. Log the response body to see the error message from the external API.
  2. Verify the Authorization header format.
  3. Ensure the API Key or Token is valid.
log.error("Auth Failed. Response: " + result.body);

Official References