9.11 - Services Workbench - Knowledge Base

Services Workbench - Knowledge Base — Knowledge Base articles.

Service Suite Knowledge Base Overview

Service Suite Knowledge Base Overview

This overview organizes Service Suite knowledge base content for current AIQ service and API testing. Remove or consolidate articles that are empty, duplicate, or tied only to obsolete examples.

Keep articles that explain

Maintenance rules

  1. Use current product names such as Service Suite and Scenario Editor where applicable.
  2. Do not include real secrets, tokens, tenant URLs, or customer-specific payloads.
  3. Prefer concise workflows and validation checklists over long one-off examples.

Last updated: September 8, 2026

Convert XML Request to JSON

This example details how to convert XML responses generated from a Services Workbench script to JSON and then back to XML using Designer .js scripts.

XML to JSON Function

Converts XML to JSON and JSON to XML.

Sample Script

XMLtoJSON&ObjtoXML.js
/**
 * @aiq.webdesigner
 * This script requires AIQ Web Designer
*/
function jsonToXml(obj) {
    // create xml string
    var xml = '';//'<?xml version="1.0" encoding="UTF-8"?>';
    //loop through and add each property as a an xml tag as well as sub properties
    for (var prop in obj) {
        if (!obj.hasOwnProperty(prop)) {
            continue;
        }
        if (obj[prop] == undefined)
            continue;
        xml += "<" + prop + ">";
        if (typeof obj[prop] == "object")
            xml += jsonToXml(new Object(obj[prop]));
        else
            xml += convertXMLString(obj[prop], 'xml');
        xml += "</" + prop + ">";
    }
    // searches the string for numbers surrounded by underscores and removes them; used for multiple properties in the json that are set up an an object instead of an array
    let reducedXML = xml.replace(/([_]\d)\w+/g, '');
    return reducedXML;
}
function convertXMLString(input, outputFormat){
  if(typeof input === 'string'){
    if(outputFormat === 'xml'){
      return input.replace(/(&)/g, '&amp;').replace(/(<)/g, '&lt;').replace(/(>)/g, '&gt;').replace(/(')/g, '&apos;')
    } else if(outputFormat === 'string'){
      return input.replace(/(&lt;)/g, '<').replace(/(&gt;)/g, '>').replace(/(&apos;)/g, "'").replace(/(&amp;)/g, '&')
    } else {
      // invalid outputFormat
      return input;
    }
  } else {
    // not a stringF
    return input;
  }
}
function parseXmlToJson(xmlString) {
  let xmlToParse = xmlString.replace(/[\r\n']/g, match => match === "'" ? "\\'" : ' '); // Replce all the new line and single quote
  const json = evalAndReturn(`
    const parser = new DOMParser();
    const xml = parser.parseFromString('${xmlToParse}', "application/xml");
    if (xml.getElementsByTagName("parsererror").length) {
      return 'Error';
    }
    const parseXmlToJson = (xml) => {
      let obj = {};
      let nodeName = xml.nodeName.includes(":") ? xml.nodeName.split(":")[1] : xml.nodeName;
      if (xml.childNodes.length === 1 && xml.firstChild.nodeType === 3) {
          return xml.textContent.trim();
      }
      for (let child of xml.childNodes) {
          if (child.nodeType === 3 && !child.textContent.trim()) {
              continue; // Ignore whitespace text nodes
          }
          let childName = child.nodeName.includes(":") ? child.nodeName.split(":")[1] : child.nodeName;
          let childJson = parseXmlToJson(child);
          if (obj[childName]) {
              if (!Array.isArray(obj[childName])) {
                  obj[childName] = [obj[childName]];
              }
              obj[childName].push(childJson);
          } else {
              obj[childName] = childJson;
          }
      }
      return obj;
    }
    const jsonObj = parseXmlToJson(xml);
    return JSON.stringify(jsonObj);
  `)
  return json;
}

Services Workbench Script

The response data is stored in locker so that it can be retrieved and used in a Designer .js script.

Sample Script

XMLJSON.js
/**
 * @aiq.servicesworkbench
 * This script requires Services Workbench
*/
var resp = get("https://mocktarget.apigee.net/xml");
var responseData = resp.getDataString();
//log(responseData);
locker.put("XMLData",responseData);

Designer Script

Includes the previous two scripts, and once the data is retrieved from locker, uses the custom .js function to parse the XML back to JSON and perform the required validations.

Sample Script

TestXMLtoJSON.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * @aiq.webdesigner
 * This script requires AIQ Web Designer
*/
include("{ds}/XMLtoJSON&ObjtoXML.js");
serviceSuite('{ds}/XMLJSON.js');
var xmlString = retrieve("XMLData");
log(xmlString);
var jsondata = parseXmlToJson(xmlString);
log(jsondata);
log(JSON.parse(jsondata));
var jsonD = JSON.parse(jsondata);
log(jsonToXml(JSON.parse(jsondata)));
assertEquals("John",jsonD.root.firstName);

Resources : Sample Scripts

You can download the sample scripts from these links.


Last updated: March 04, 2025

If you have any feedback on this topic, please contact us at documentation@appvance.ai

How to add DPL reference in-service suite script vs Scenario

Using the below syntax we can run the DPL enabled script from the service suite as well as from the scenario editor without having to change anything from the code.

setVariablesIfNeeded('{ds}/globalProperties.csv','HashDPL',0);


Last updated: June 02, 2023

If you have any feedback on this topic, please contact us at documentation@appvance.ai

How to use Path Parameters in Service Suite?

Path parameters are dynamic values that are included directly in the URL path of an API endpoint.

In Service Suite, you can construct an endpoint URL using JavaScript string concatenation. This allows you to insert variables, such as a client number or customer ID, into the URL.

Example

The following example uses the $clientNumber variable as a path parameter in a POST request:

var $clientNumber = getLoggedClientNumber(); // Or read the value from a CSV file

var $url = $server + "/customer/" + $clientNumber + "/reports";

var $payload = {
    "action": "listinvoices",
    "from": "10/01/2020"
};

var $response = post($url, $payload);

var $list = JSON.parse($response.getDataString());

How it works

If $server contains:

https://example.com

and $clientNumber contains:

12345

the resulting URL will be:

https://example.com/customer/12345/reports

In this example:

Using a CSV Value

The path parameter can also come from test data, such as a CSV file:

var $clientNumber = $clientNumberFromCSV;

var $url = $server + "/customer/" + $clientNumber + "/reports";

var $response = post($url, $payload);

This approach allows the same Service Suite script to execute the request for different client numbers without changing the endpoint manually.

Summary

Use JavaScript string concatenation when a value in the API endpoint needs to change dynamically during execution:

var $url = $server + "/customer/" + $clientNumber + "/reports";

This is useful for endpoints that follow patterns such as:

/customer/{clientNumber}/reports
/customer/{customerId}/invoices
/orders/{orderId}/details

Last updated: August 17, 2026

If you have any feedback on this topic, please contact us at documentation@appvance.ai.

How to use Query Parameters in Service Suite?

Query parameters are values added to the URL after the ? character. They are commonly used to provide optional parameters or filters to an API request.

In Service Suite, you can construct a URL dynamically using JavaScript string concatenation and variables.

Example

The following example uses $clientNumber as a query parameter in a GET request. It also adds a timestamp as a cache-busting parameter:

var $clientNumber = getLoggedClientNumber(); // Or read the value from a CSV file

var $url = $server + "/common/getClientData/?clientNumber=" + $clientNumber + "&_=" + Date.now();

var $response = get($url);

var $json = JSON.parse($response.getDataString());

var $clientNumber = $json.clientNumber;

How it works

If $server contains:

https://example.com

and $clientNumber contains:

12345

the resulting URL will look similar to:

https://example.com/common/getClientData/?clientNumber=12345&_=1750000000000

The URL contains two query parameters:

Date.now() returns the current time in milliseconds since the Unix epoch.

Multiple Query Parameters

When an endpoint requires multiple query parameters, use ? before the first parameter and & before each additional parameter.

For example:

var $url = $server + "/common/getClientData/?clientNumber=" + $clientNumber + "&status=active";

This produces a URL such as:

https://example.com/common/getClientData/?clientNumber=12345&status=active

Query Parameters vs. Path Parameters

A path parameter is part of the URL path:

/customer/12345/reports

A query parameter is included after ?:

/customer/reports?clientNumber=12345

The API determines which format should be used for each endpoint.

Summary

Use JavaScript string concatenation to dynamically construct URLs containing query parameters:

var $url = $server + "/common/getClientData/?clientNumber=" + $clientNumber;

When adding more than one query parameter, separate them with &:

var $url = $server + "/common/getClientData/?clientNumber=" + $clientNumber + "&status=active";

Last updated: August 1, 2026

If you have any feedback on this topic, please contact us at documentation@appvance.ai.

How to get DPL data in the service suite?

The below code iterates on the columns and fetches the DPL data we want to use in the script.

setVariablesIfNeeded('test.csv','HashDPL',0,'en_US');
var Length = 5;
for (j=1; j<=Length; j++) {
  var val = eval("$Test"+j);
  log(val);
}

Example Code

test.csv


Last updated: June 02, 2023

If you have any feedback on this topic, please contact us at documentation@appvance.ai

How to Validate API Responses in Service Suite

When executing API requests in Service Suite, you can validate the response to verify that the request was successful.

You can validate information from the response body, such as a success flag or message, as well as HTTP response properties such as the status code.

Validate a Response Using JavaScript

The following example parses the response body and throws an error if the API indicates that the operation was not successful:

var $response = post($url, $payload);

var $jsonList = JSON.parse($response.getDataString());

if ($jsonList.isSuccess !== "true") {
    throw "Call was not successful. Message: " + $jsonList.message;
}

In this example:

Validate a Response Using assertEquals

Instead of using an if statement, you can use an assertion to validate a value in the response:

var $response = post($url, $payload);

var $jsonList = JSON.parse($response.getDataString());

assertEquals($jsonList.isSuccess, "true");

The assertion fails if isSuccess does not equal "true".

Validate the HTTP Status Code

You can also validate the HTTP status code returned by the server:

assertEquals($response.status, 200);

This verifies that the response status is 200 (OK).

For example, you can combine a status-code validation with a response-body validation:

var $response = post($url, $payload);

assertEquals($response.status, 200);

var $jsonList = JSON.parse($response.getDataString());

assertEquals($jsonList.isSuccess, "true");

This allows you to verify both:

  1. The HTTP request returned the expected status code.
  2. The API response body contains the expected result.

Summary

Service Suite provides multiple ways to validate API responses:

Using these validations helps ensure that API requests not only execute successfully but also return the expected results.


Last updated: September 1, 2026

If you have any feedback on this topic, please contact us at documentation@appvance.ai.

How is the Request and response POJO management?

Internally when you call a createPost() command (or createGet, createOptions, etc), it returns an HttpRequest object. In the following snippet, the $call variable is an instance of an HttpRequest object that when processed will trigger a POST call:

var $call = createPost($url, $payload);
var $call.addHeader("Authorization","Basic "+$TOKEN);
var $response = process($call);
log("Status: "+$response.status);

And the variable $response is an instance of an HTTPResponse object.

Some of the most commonly used properties and methods can be found in the following examples.

HttpRequest

var $data = { "some":"data"};
var $call = createPost("https://www.apple.com",$data);
$call.addHeader("someheader","somevalue");
log($call.getHeader("content-type"));
log($call.postMimeType());
log($response.recreateHeader());  // returns request headers raw text
log($call.url());
log($call.contentLength());
log($call.getDataString());

HttpResponse

var $response =get("http://worldclockapi.com/api/json/est/now");
log($response.status);       // 200
log($response.statusText);   // OK
log($response.getDataString());
log($response.getHeader("Server"));
log($response.recreateHeader());  // returns response headers raw text
log($response.getResponseType()); // JSON
log($response.contentLength());
log($response.isBasic());
log($response.isNTLM());
log($response.isNegotiate());

Last updated: June 02, 2023

If you have any feedback on this topic, please contact us at documentation@appvance.ai

How to validate status code that is returned in response header

If we want to validate the status code that is returned in the response header we can use the below approach.

var $response = post($url, $payload );
assertEquals($response.status, 200);

Last updated: June 02, 2023

If you have any feedback on this topic, please contact us at documentation@appvance.ai

How to read specific row in service suite from CSV?

By providing the number after the csv, Service suite should be able to read from the specific row.

HashDPL(“{mds}/data/data.csv”,1);



Last updated: February 17, 2023

If you have any feedback on this topic, please contact us at documentation@appvance.ai

Reading data from JSON/XML from Service Suite

You can read JSON data from a file and convert it into a JavaScript object using the following example:

// Read the JSON file and convert its contents to a string.
var $aJsonString = new java.lang.String(
    readBytes("{mds}/somejsonfile.json")
);

// Parse the JSON string into a JavaScript object.
var jsonObj = JSON.parse($aJsonString);

The example performs two steps:

  1. Read the file: readBytes() reads the contents of the JSON file. The contents are converted to a Java String.
  2. Parse the JSON: JSON.parse() converts the JSON string into a JavaScript object, allowing you to access individual values from the JSON data.

For example, if the JSON file contains:

{
  "username": "mauricio",
  "environment": "QA"
}

You can access its values through the resulting object:

log(jsonObj.username);
log(jsonObj.environment);

This produces:

mauricio
QA

Last updated: August 13, 2026

If you have any feedback on this topic, please contact us at documentation@appvance.ai.

How to add transaction names in Service Suite

You can use the startStep() API to assign a descriptive name to a transaction or step in a Service Suite script. The transaction name is then displayed in the execution reports, making it easier to identify and analyze individual transactions.

Example

The following example assigns the transaction name "Open web page API simulation" before executing the request:

addGlobalHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36");

addGlobalHeader("cache-control", "no-cache");

startStep("Open web page API simulation");

var resp = open("https://www.apple.com");

How it works

You can replace the transaction name with any meaningful description that helps identify the operation being measured.

For example:

startStep("Login API");

var resp = open("https://example.com/login");

This allows the transaction to be easily identified as "Login API" in the corresponding reports.


Last updated: August 17, 2026

If you have any feedback on this topic, please contact us at documentation@appvance.ai.

Generating thousands/millions of rows of Synthetic DPL data using Rest APIs

Below is the Service Suite script that can be used to generate synthetic data using Rest APIs, this would be useful when generating millions of data which might be difficult using the UI as UI will have its own limitations.

Start AIQ

Go to Service Suite

API editor

Open the below script

modify the quantity variable to 1000000 (or as desired)

Save and run

a file with 1000000 will be created.

Note: There are 2 parameters that might be required to change.

  1. AIQ URL (Line 1) - var $baseURL = "http://localhost:8080";

  2. Creds (Line 12 and 13) - Username and password.

var $baseURL = "http://localhost:8080";

function getTimestamp() {
    return (new Date()).getTime();
}

//
// LOGIN
//
function login($baseURL) {
    var $loginForm = formData();
    $loginForm.put("username", "appvance");
    $loginForm.put("password", "appvance");
    var $call = createPost($baseURL + "/AppvanceServer/rest/admin/logginForServices", $loginForm);
    var $resp = process($call).getDataString();
    if ($resp.indexOf("true") != -1) {
        //
        // GET JSESSIONID COOKIE
        //
        var $jsession = getCookieValue($baseURL, "JSESSIONID");
        addGlobalHeader("cookie", "JSESSION = " + $jsession);
        log("Login successful, JSESSION:" + $jsession)
        return true;
    } else {
        return false;
    }
}

var $resp = login($baseURL);
var quantity = 100000;
var fileName = "REPOSITORY/HOME/DPL.csv";

if ($resp === true) {

    log("Is Logged");
    var data = [["qty", "test"],["syn.random.number(10)","syn.random.number(100)"]];
    var call = createPost($baseURL + "/AppvanceServer/rest/syntheticDPL/evaluate?lines="+quantity+"&locale=en_US", data);
    var stringJson = process(call).getDataString();  
    var payload = JSON.parse(stringJson.replace(/\n|\r/g, "").trim());
    var callSave = createPost($baseURL + '/AppvanceServer/rest/file/updateCSVFile?commitMessage=&path=' + fileName, payload);    
    var stringJsonSave = process(callSave).getDataString();  
    var response = JSON.parse(stringJsonSave);
    log(fileName + " was saved: " + response.status); 
}
//
// LOGOUT
//
var $resp = get($baseURL + "/AppvanceServer/rest/admin/logout");
log("logout: " + $resp.getDataString());

Last updated: June 02, 2023

If you have any feedback on this topic, please contact us at documentation@appvance.ai

Run Approved OS Commands from Service Suite

Last reviewed: September 30, 2026

Use this article only when a Service Suite scenario must run an approved operating-system command through AIQ in a controlled execution environment.

What changed in this article

Validation workflow

  1. Confirm the command is approved for the target AIQ environment and cannot expose credentials or modify unintended files.
  2. Validate the working directory, arguments, permissions, expected output, timeout, and failure behavior in a non-production run.
  3. Review stdout, stderr, exit status, and Service Suite logs before using the command in a scheduled or shared workflow.

Review note: This page is now intentionally cautious because OS command execution can affect the execution host.


Last updated: September 30, 2026

If you have feedback on this topic, contact documentation@appvance.ai.

Validate CAPTCHA Text Capture in Approved Test Environments

Validate CAPTCHA Text Capture in Approved Test Environments

Use this article only for approved test environments where CAPTCHA images are provided as controlled test fixtures. Do not use AIQ automation to bypass CAPTCHA or defeat anti-bot protections on production systems.

Safe testing approach

Validation workflow

  1. Confirm written approval or documented test-mode support for the CAPTCHA scenario.
  2. Run the test with a known fixture and expected extracted value.
  3. Validate success and failure behavior separately.
  4. Review logs to ensure no real user credentials, session data, or sensitive images are captured.

Security note: CAPTCHA is a security control. This content must not be used as a guide for bypassing CAPTCHA on live systems.

Last updated: October 8, 2026

Gmail App Password Use in Service Suite Tests

Last reviewed: September 24, 2026

This legacy article applies only when a Google account is explicitly allowed to use app passwords for automated Service Suite mail validation.

What changed in this article

Validation workflow

  1. Confirm that the Google Workspace policy permits app passwords for the selected test account.
  2. Create the password outside AIQ and store it only in the approved secret manager.
  3. Run the Service Suite script with a small mailbox test and verify no secret value appears in output.

Review note: Marked the Gmail app-password flow as legacy/security-sensitive and added OAuth/test-mail alternatives.


Last updated: September 24, 2026

If you have feedback on this topic, contact documentation@appvance.ai.

Outlook App Password Use in Service Suite Tests

Last reviewed: September 24, 2026

This legacy article applies only when an Outlook or Microsoft 365 account still allows app passwords for automated Service Suite login flows.

What changed in this article

Validation workflow

  1. Confirm with the Microsoft 365 administrator that app passwords are allowed for the tenant and test account.
  2. Generate the app password outside AIQ and store it only in the approved secret location.
  3. Run the Service Suite script against a non-production mailbox and remove any captured secret from logs.

Review note: Marked the Outlook app-password approach as legacy/security-sensitive and replaced the long title with a concise scope-focused title.


Last updated: September 24, 2026

If you have feedback on this topic, contact documentation@appvance.ai.

Validating Emails using Service Suite Scripts

  1. Create a Script in Service suite as shown below:

  2. Please refer the below document if the outlook account as 2FA authentication:

    How to create an App Password for an Outlook account and use in service suite script to login to account with 2FA enabled.

Note: Below is the spec on AIQ mail client

Mail Client

>createMailClient(String host, String userMail, String password, String protocol)

returns a MailService object.

>MailService object has the following available methods:

· List<String> getAttachments() //You can get a list with all the names of the attachments from a MailMessage and you can get the file (byte[]) using the file name.

Sample code snippet

var mailClient = createMailClient("imap.gmail.com", "myEmail@gmail.com", "App Password", "IMAP");

try{
  mailClient.login();
}
catch (e){
  mailClient.login();
}
log("Login Outlook Account");
mailClient.setFromFolder("Inbox");
log("Navigate to the Inbox folder");
//mailClient.setFromFolder("Junk Email");

var lastUnreadMails = mailClient.getLastUnreadMessages(5);
var url;
var attachments;

//log(lastUnreadMails.size());

for(var i = 0; i < lastUnreadMails.size(); i++){
  if(lastUnreadMails.get(i). getFrom().  contains("Appvance")){
      log("Fetch the url from this subject" + lastUnreadMails.get(i).getSubject());
      log("From "+lastUnreadMails.get(i).getFrom());  
      log("Date "+lastUnreadMails.get(i).getSentDate());
      log("Folder "+lastUnreadMails.get(i).getFolder());
      log("getRecipients "+lastUnreadMails.get(i).getRecipients());
      log("Fetch the url from this body" + lastUnreadMails.get(i).getBody());
      attachments = lastUnreadMails.get(i).getAttachments();
      for(var j = 0; j < attachments.size(); j++){
          log("Attachment name: "+attachments.get(j));
          var bytes =  lastUnreadMails.get(i).getAttachedFile(attachments.get(j));
          locker.put("bytes",bytes);
          writeBytes("newBytes.pdf", bytes);
      }
    //code = urlify(lastUnreadMails.get(i).getBody());
  }
 // log("Fetch the url from this body" + lastUnreadMails.get(i).getSubject());
}
mailClient.logout();

Note: Replace the valid Email account to test the above methods.

Outlook Sample

var mailClient = createMailClient("imap.outlook.com", "aiqtest@outlook.com", "password", "IMAP");

mailClient.login();

mailClient.setFromFolder("Inbox");

var lastUnreadMails = mailClient.getLastUnreadMessages(1);

var url = "";

var from=lastUnreadMails.get(0).getFrom().contains("Appvance");

log(from);

Last updated: June 02, 2023

If you have any feedback on this topic, please contact us at documentation@appvance.ai

Microsoft Graph: Send, Read, Reply, and Delete Email in Service Suite

Microsoft Graph: Send, Read, Reply, and Delete Email in Service Suite

Use this article when a Service Suite workflow needs to validate Microsoft Graph email operations in an approved test mailbox. Do not run destructive email actions against real customer or employee mailboxes unless the customer has explicitly approved the test.

Configuration checklist

Validation workflow

  1. Confirm token acquisition and Graph endpoint access with the test mailbox.
  2. Send or locate a controlled test message.
  3. Validate the response status, message ID, subject, recipient, and body fields needed by the test.
  4. Reply or delete only the controlled message created for the test run.
  5. Review logs to confirm secrets and message content are not exposed unnecessarily.

Last updated: October 8, 2026

Sample Service Suite Scripts

Here are some Service Suite samples scripts that can be used as a reference.

Basic Authentication


                                                    
/**
 * btoa
 * @param st 
 * @returns base64 encoded string
 */
 function btoa(st) {
    return Java.type("java.util.Base64").getEncoder().encode(st);
}

var $username = "admin";
var $password = "Password@1";
var $TOKEN = "Basic "+btoa($username+":"+$password);

addGlobalHeader("authorization", $TOKEN);

var url = "https://someServer.com/api/somecall";
var data = {"short_description":"some data",
    "description":"some description"};

var call = createPost(url, data);
call.addHeader("Content-Type", "application/json");

var resp = process(call);

                                                

Demosite

var formPage = get("https://demosite.appvance.com/products/ruby-on-rails-tote");
log(getCookies("https://demosite.appvance.com/"));
var token = extract("html",formPage,'//form[@action="/orders/populate"]/input[@name="authenticity_token"]/@value');
log("Extracted token: "+token);
 
var form = formData();
form.put("authenticity_token",token);
form.put("utf-8",decodeURIComponent("%E2%9C%93"));
form.put("variant_id","1");
form.put("quantity","1");
form.put("button","");
 
var postData = createPost("https://demosite.appvance.com/orders/populate",form);
var purchaseResults = process(postData);
 
log("response:\n"+purchaseResults.getDataString());
 
assertContains(get("https://demosite.appvance.com/cart?variant_id=1").getDataString(),"Ruby on Rails Tote");
assertContains(get("https://demosite.appvance.com/cart_link").getDataString(),"$15.99");

Login to AIQ

$baseURL = "http://localhost:8080";
var $loginForm = formData();

$loginForm.put("username","appvance");
$loginForm.put("password","appvance");

var $call = createPost($baseURL+"/AppvanceServer/rest/admin/loggin", $loginForm);
var $resp = process($call);
var $json = JSON.parse($resp.getDataString());

if ($json.logged) {
  var $jsession = getCookieValue($baseURL,"JSESSIONID");
  addGlobalHeader("cookie","JSESSIONID="+$jsession);  // adding global cookie for JSESSIONID
  log("Logged In, JSESSIONID: "+$jsesssion);
} else {
  log("Unsuccessful login");
}

ServiceSuite and XML

var payload = "<xml><username>John</username><id>123-1341-123</id></xml>";
var call = createPost("https://example.com/getAddress", payload, "text/xml");
var result = process(call);
var address1 = extract("XML", result, "//Address1");
var city = extract("XML", result, "//City");
var state = extract("XML", result, "//ST");

Upload a file to AIQ repository

const $boundary = "AIQ-DS-BOUNDARY";

function login($baseURL, $username, $password) {
    var $loginForm = formData();
    $loginForm.put("username",$username);
    $loginForm.put("password",$password);
    var $call = createPost($baseURL + "/AppvanceServer/rest/admin/logginForServices", $loginForm);
    var $resp = process($call).getDataString();
    if ($resp.indexOf("true") != -1) { 
        var $jsession = getCookieValue($baseURL,"JSESSIONID");
        addGlobalHeader("cookie","JSESSION=" + $jsession);
        log("Login successful")
        return true;
    } else {
        log("Login was NOT successful")
        return false;
    }
}


function createMultipartPayload($filename) {
    var header = '--' + $boundary + '\n' +
        'Content-Disposition: form-data; name="file"; filename="' + $filename + '"\r\n' +
        'Content-Type: application/octet-stream\r\n\r\n';
    var headerBytes = new Array(header.length);
    for (var i = 0; i < header.length; ++i) {
        headerBytes[i] = header.charCodeAt(i);
    }
    var docBytes = readBytes($filename);
    var footer = '\r\n--' + $boundary + "--\r\n";
    var footerBytes = new Array(footer.length);
    for (var i = 0; i < footer.length; ++i) {
        footerBytes[i] = footer.charCodeAt(i);
    }
    return headerBytes.concat(docBytes).concat(footerBytes);
}


var $filename = "test2.js";    // CHANGE FOR YOUR FILE TO UPLOAD
var $repositoryName = "test";  // CHANGE FOR YOUR REPO NAME, THIS CONF MUST BE IN THE USER ACCOUNT USED TO LOGIN
var $username = "appvance";
var $password = "appvance";

login("http://my-aiq-controller.appvance.com:8080", $username, $password);   // CHANGE FOR YOUR AIQ SERVER DNS

var data = createMultipartPayload($filename);
log(data.length, $username, $password);

var call= createPost("http://localhost:8080/AppvanceServer/rest/file/uploadFile?&commitMessage=Appvance+skip+commit&path=REPOSITORY/"+$repositoryName+"/"+$filename, data, "multipart/form-data", " boundary=" + $boundary);

var resp = process(call);
log(resp.getDataString())

Upload the File to an application

/**
 * Creates a multipart payload from file contentes
 **/
function createMultipartPayload($filename) {
    var header = '--' + $boundary + '\n' +
        'Content-Disposition: form-data; name="file"; filename="' + $filename + '"\r\n' +
        'Content-Type: application/octet-stream\r\n\r\n';
    var headerBytes = new Array(header.length);
    for (var i = 0; i < header.length; ++i) {
        headerBytes[i] = header.charCodeAt(i);
    }
    var docBytes = readBytes($filename);
    var footer = '\r\n--' + $boundary + "--\r\n";
    var footerBytes = new Array(footer.length);
    for (var i = 0; i < footer.length; ++i) {
        footerBytes[i] = footer.charCodeAt(i);
    }
    return headerBytes.concat(docBytes).concat(footerBytes);
}

/*
 * Uploads a file to $SERVER
 */
function uploadFile($filename) {
   var data = createMultipartPayload($filename);
   var url = $SERVER+ "/upload/"+$filename+"?filename="+$filename;
   log("url: "+url);
   var call= createPut(url, data, "multipart/form-data", " boundary=" + $boundary);
   return process(call).getDataString();
}

const $boundary = "AIQ-DS-BOUNDARY";
const $SERVER = "http://xxxx.eu-west-2.elb.amazonaws.com";

var $filename = "test2.js";    // CHANGE FOR YOUR FILE TO UPLOAD
var result = uploadFile($filename);

log(result);

Send an email with a custom timestamp in the Subject

var date = new Date();
var formatDate = date.getDate() + "-" + date.getMonth() + "-" + date.getYear() + " " + date.toLocaleTimeString();
var graphService = createOAuthMailClient("email", "clientID", "secretKey", "tenantID");
var msg = graphService.createMailMessage("Message generated " + formatDate, "Test body email.");
msg.addTo(["to@test.com"]);
var sent = graphService.sendMessage(msg);
if (!sent) {
    throw new Error('An error occurred while sending the email.');
} else {
    log("Email sent.");
}

Extract value from receive email’s subject

var graphService = createOAuthMailClient("email", "clientID", "secretKey", "tenantID");
var unreadList = graphService.getUnreadMessages();
var unreadCount = unreadList.size();
if (unreadCount > 0) {
  var i = 0; //first unread email
  log("Id: " + unreadList.get(i).id);
  log("From: " + unreadList.get(i).from.emailAddress.address);
  log("Subject: " + unreadList.get(i).subject);
    
  var subject = unreadList.get(i).subject;
  var startIndex = subject.lastIndexOf("Ref. ") + 5;
  var refCode = subject.substr(startIndex, 8);
  log("Ref code: " + refCode);

Compare email’s body

var expectedBody = "This is an automated acknowledgement to let you know that your correspondence to the Appvance Information Rights Team has been received.Any request for personal data will be handled as a data subject access request.";
  var body = unreadList.get(i).body.content.replace(/(\r\n|\n|\r)/gm,""); //remove line break and spaces.
  body = body.substr(0,body.indexOf("uk")+2); //trim to remove links at the end
  log("Email have the expected content: " + (body === expectedBody));
} else {
    log("There's no unread messages.");
}


Utility Functions

/**
 * btoa
 * @param st 
 * @returns base64 encoded string
 */
 function btoa(st) {
    return Java.type(java.util.Base64).getEncoder().encode(st);
}

/**
 * atob
 * @param st 
 * @returns decoded base64 string
 */
function atob(st) {
    return Java.type(java.util.Base64).getDecoder().decode(st);
}
/**
 * 
 * @param length number
 * @returns random number of length number of digits
 */
function randomNumber(length) {
    var result = '';
    for (var i = 0; i < length; i++) {
        result += Math.floor(Math.random() * 10);
    }
    return result;
}


/**
 * Extracts catching an exception and returning NOT_FOUND string if target is not found.
 * @param {string} format ("HTML","HTTP","PDF","CSV","Excel","TEXT","STRING")
 * @param {HttpResponse} response 
 * @param {string} target 
 * @returns 
 */
function extractOrNotFound(format, response, target) {
    const NOT_FOUND = "Not found.";
    var value;
    try {
        value = extract(format, response, target);
        if (typeof value == "undefined") {
            return NOT_FOUND;
        }
        return value;
    } catch (e) {
        return NOT_FOUND;
    }
}

/**
 * SSN
 * @returns random string matching social security number format
 */
function SSN() {
    var result = '';
    result = randomNumber(3) + "-" + randomNumber(2) + "-" + randomNumber(4);
    return result;
}

// *************************************************************************
// parseCurrencyStringToNumber
//
// Parse a currency string to a number, $1,245.50 -> 1245.5
// ************************************************************************
function parseCurrencyStringToNumber(currency) {
    if (typeof currency == "undefined" || currency == "") {
        log("Warning in parseCurrencyStringToNumber, expecting a number but got undefined or empty.");
        return -1;
    }
    if (currency == "0") {
        return 0;
    }
    if (currency.indexOf("(") !== -1) {
        currency = "-" + currency.replace(/[(|)]+/g, "")
    }
    return Number(String(currency).replace(/[^0-9.-]+/g, ""));
}

// *************************************************************************
// parseNumberToCurrency
//
// Parse a number to a string currency with commas, 1245.5  -> $1,245.50 
// ************************************************************************
function parseNumberToCurrency(aNumber) {
    if (aNumber == 0) {
        return ("$0.00");
    };
    var tempST = String(aNumber).replace(/[^0-9.-]+/g, "");
    try {
        var tempNumber = Number(parseFloat(tempST).toFixed(2));
        var tempLocale = tempNumber.toLocaleString(undefined, { minimumFractionDigits: 2 });
        return "$" + tempLocale;
    } catch (e) {
        log("Error converting to currency [" + aNumber + "], isNAN: " + isNaN(aNumber) + " - " + e.message);
        return "NA";
    }
}

/**
 * dateToString
 * @param day 
 * @returns 
 */
function dateToString(day) {
    var dd = String(day.getDate()).padStart(2, '0');
    var mm = String(day.getMonth() + 1).padStart(2, '0'); //January is 0!
    var yyyy = day.getFullYear();
    return mm + '/' + dd + '/' + yyyy;;
}

function getTodayDate() {
    var now = java.time.LocalDateTime.now();
    var formatter = java.time.format.DateTimeFormatter.ofPattern("MM/dd/yyyy");
    return now.withHour(0).withMinute(0).withSecond(0).format(formatter);
}

function getYesterdayDate() {
   var now = java.time.LocalDateTime.now();
   var formatter = java.time.format.DateTimeFormatter.ofPattern("MM/dd/yyyy");
   return now.minus(1, java.time.temporal.ChronoUnit.DAYS).withHour(0).withMinute(0)
                .withSecond(0).format(formatter);
}

function getTomorrowDate() {
   var now = java.time.LocalDateTime.now();
   var formatter = java.time.format.DateTimeFormatter.ofPattern("MM/dd/yyyy");
   return now.plus(1, java.time.temporal.ChronoUnit.DAYS).withHour(0).withMinute(0)
                .withSecond(0).format(formatter);
}

/**
 * getDateISOString
 * @returns 
 */
function getDateISOString() {
    var st = new Date().toISOString();
    return st.replace(/\-/g, "_").replace(/\:/g, "_").replace(/\./g, "_");
}

/**
 * dateOneMonthAfter
 * @param $date 
 * @returns 
 */
function dateOneMonthAfter($date) {
    var day = new Date(Date.parse($date));
    day.setMonth(day.getMonth() + 1);
    day.setTime(day.getTime() + TIME_OFFSET);
    return dateToString(day);
}

/**
 * dateOneYearAfter
 * @param $date 
 * @returns 
 */
function dateOneYearAfter($date) {
    var day = new Date(Date.parse($date));
    day.setFullYear(day.getFullYear() + 1);
    day.setTime(day.getTime() + TIME_OFFSET);
    return dateToString(day);
}

/**
 * randomLastName
 * @returns 
 */
 function randomLastName() {
    var sylabs = 2 + Math.floor(Math.random() * 2)
    var result = '';
    var characters = 'bccdfghjkllmnnprrssttvwyz';
    var vocals = 'aeioeua';
    var charactersLength = characters.length;
    result += "SBCLKJMCH".charAt(Math.floor(Math.random() * 9));
    result += vocals.charAt(Math.floor(Math.random() * 7));
    sylabs--;
    for (var i = 0; i < sylabs; i++) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
        result += vocals.charAt(Math.floor(Math.random() * 7));
    }
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
    return result;
}

                                                

Last updated: June 02, 2023

If you have any feedback on this topic, please contact us at documentation@appvance.ai

Run Node.js npm Code from Service Suite

Run Node.js npm Code from Service Suite

Use this guidance when a Service Suite scenario needs to call Node.js code or an npm package. Keep package execution controlled, repeatable, and limited to approved dependencies.

Before execution

Validation workflow

  1. Run the Node.js code with a small local or non-production sample.
  2. Confirm Service Suite can locate the script and dependencies.
  3. Review output and failures before using the package in a suite or scheduled run.
  4. Record dependency changes with the scenario maintenance notes.

Last updated: September 8, 2026

Send Email from Service Suite

Use this article when a Service Suite workflow needs to send email through an approved mail provider or internal SMTP path.

What changed in this article

Validation workflow

  1. Confirm the approved mail provider, sender identity, recipient list, and allowed test data before sending email.
  2. Validate authentication, TLS, request payload, response status, and retry/error behavior in a controlled run.
  3. Review logs and received messages without exposing passwords, tokens, or sensitive customer data.

Review note: This page now treats email sending as an integration with data-transmission risk controls.


Last updated: October 5, 2026

If you have feedback on this topic, contact documentation@appvance.ai.

RBAC setup to use Graph Service in Service Suite

RBAC (Role-Based Access Control) for applications will eventually replace application access policies (see below). Today, the public preview shows some ragged edges in terms of the integration between Exchange Online and Azure AD that Microsoft says they’ll fix before general availability. Even with some bumps to navigate, the new functionality is a welcome addition that extends tenant control over their data, which is always a good thing.

In order to use this protocol you need to do the following configurations in your azure account so the roles and permissions are set correctly.

Once you have done the below steps you should be able to use the service suite Graph Service oauth2 client to send/receive emails using RBAC with the normal client commands.

  1. Open Windows PowerShell as an Administrator

  2. Execute: connect-mggraph -scopes "Application.Read.All". If the exception "The term connect-mggraph' is not recognized as the name of a cmdlet" appears, execute the command
    install-module microsoft.graph

  3. After Microsoft Graph is installed, execute again the command connect-mggraph -scopes "Application.Read.All"

  4. Sign in

  5. After signing in, should see this:

  6. Execute: $SP = Get-MgServicePrincipal -All

  7. Execute: $ServicePrincipalId = $SP | Where-Object {$_.displayName -eq "{Application name}"} | Select-Object -ExpandProperty Id

  8. Execute: $AppId = $SP | Where-Object {$_.displayName -eq "{Application name}"} | Select-Object -ExpandProperty AppId

  9. Execute: Write-Host ("AppId is {0} and Service Principal Id is {1}" -f $AppId, $ServicePrincipalId)
    Output:

  10. Execute: Install-Module -Name ExchangeOnlineManagement

  11. Execute: Import-module ExchangeOnlineManagement

  12. Execute: Connect-ExchangeOnline -UserPrincipalName {user email}
    Output:

  13. Execute: New-ServicePrincipal -AppId $AppId -ServiceId $ServicePrincipalId -DisplayName '{Application name}'

  14. Execute: New-ManagementScope -Name "Management emails" -RecipientRestrictionFilter "CustomAttribute1 -eq '{RolName}'"

  15. Execute: Set-Mailbox -Identity {username} -CustomAttribute1 "{RolName}"

  16. Execute: Get-EXOMailbox -Filter {CustomAttribute1 -eq "{RolName}"} | Format-Table DisplayName

  17. Execute: New-ManagementRoleAssignment -App $AppId -Role "Application Mail.Mail.ReadWrite" -CustomResourceScope "Management emails"

  18. Execute: New-ManagementRoleAssignment -App $AppId -Role "Application Mail.Send" -CustomResourceScope "Management emails"

Assing a role to a user

Execute: Set-Mailbox -Identity {username} -CustomAttribute1 "{RolName}"

List available permissions

Execute: Get-ManagementRole | Where-Object {$_.Name -like "Application *"}

See permissions assigned to a user

Execute: Test-ServicePrincipalAuthorization -Identity $AppId -Resource {email}


Last updated: May 26, 2023

If you have any feedback on this topic, please contact us at documentation@appvance.ai