How to loop through the REST API GET requests in server-side JavaScript

How to loop through the REST API GET requests in server-side JavaScript

This article explains how to efficiently loop through the REST API GET requests in Salesforce Marketing Cloud.

What’s with all the loops?

Salesforce Marketing Cloud has a robust API that allows us to retrieve a lot of different information about the different parts of the application.

For instance, we can retrieve the names of all the DataExtensions, the details of all the Journeys or, why not, all the HTML blocks from the Content Builder.

That being said, when it comes to the GET requests from the REST API, things can get a bit tricky, as the number of items that can be retrieved per request is fixed to a maximum amount.

Let’s examine the curious case of Marketing Cloud Journeys and how to retrieve them all in the most efficient way possible by using the technique of the loop.

Retrieve all the Journeys

In order to retrieve all the Journeys from a Marketing Cloud Business Unit, we simply need to send a REST API GET request to the following endpoint: /interaction/v1/interactions

Not entirely true!

As stated in the official documentation, the result of this GET request is paginated and returns a maximum amount of 50 items per page.

This means that if there are 75 Journeys in our Business Unit, the request will only retrieve 50.

{
  "count": 75,
  "page": 1,
  "pageSize": 50,
  "links": {},
  "items": [...]
}

How do we fix that? How can we possibly retrieve all the Journeys from all the pages of the GET request?

Tales of the loop

Looping through the GET requests requires some optimisation, as we don’t want to perform more requests than necessary.

In order to achieve that, we first need to create a dedicated function for performing the GET requests.

function performGetRequest(url, token) {

    var req = new Script.Util.HttpRequest(url);
        req.emptyContentHandling = 0;
        req.retries = 2;
        req.continueOnError = true;
        req.setHeader("Authorization", "Bearer " + token);
        req.method = "GET";
        req.contentType = "application/json";
        req.encoding = "UTF-8";

    var res = req.send();

    return Platform.Function.ParseJSON(String(res.content));
}
Note: this function requires an endpoint URL and a REST API token. Learn how to retrieve them thanks to my previous article about How to create, store and use REST API tokens in Salesforce Marketing Cloud.

Now, let’s create a function to loop through the GET requests and retrieve all the Journeys available.

How it works

  1. We perform an initial request to retrieve the first 50 items and the count of the total items available.
  2. If the total count is greater than 50, it means that we need more requests in order to retrieve the rest of the items.
  3. We determine how many loops we need to perform by dividing the total count by the number of items returned per page.
  4. Considering we already have the items from the first page, we need to start the loop at page #2.
  5. Looping through the pages in a GET request is a matter of providing a $page parameter at the end of the URL.
  6. We need this function to return an array of objects and therefore a simple for loop is necessary to assemble all the items from all the pages.
function retrieveAllJourneys() {

    var output = [];

    var page = 1;
    var pageSize = 50;

    var requestURL = BASE_URL + "interaction/v1/interactions?$page=" + page + "&$pageSize=" + pageSize;  

	// #1
    var result = performGetRequest(requestURL, TOKEN);

    if(result.count == 0) return [];

    output = result.items;
	
	// #2
    if(result.count > pageSize) {

		// #3
        var loops = result.count / pageSize;

		// #4
        for(var i = 1; i < loops; i++) {

            var nextPage = i + 1;

			// #5
            var requestURL = BASE_URL + "interaction/v1/interactions?$page=" + nextPage + "&$pageSize=" + pageSize; 

            var result = performGetRequest(requestURL, TOKEN)

            var items = result.items;

			// #6
            for(var k in items) {

                var item = items[k];

                output.push(item);

            }

        }

    } 

    return output;
}

Full code

All is left is to integrate these 2 functions within a SSJS that retrieves a REST API token and publish it on a Cloud page or execute it from a Script Activity in an Automation.

Note: please replace everything between {{ ... }} with the data from your account.
<script runat='server'>
    Platform.Load('core', '1');

    var api = new Script.Util.WSProxy();

    var config = {
        endpoint: "https://{{ DOMAIN }}.auth.marketingcloudapis.com/",
        bu: Platform.Function.AuthenticatedMemberID(),
        credentials: {
            "{{ BU ID }}": {
                "client_id": "{{ CLIENT ID }}",
                "client_secret": "{{ CLIENT SECRET }}",
                "grant_type": "client_credentials"
            }
        },
        storage: {
            de: "REST_Tokens",
            name: "JB_ALL"
        },
        localDate: DateTime.SystemDateToLocalDate(Now())
    }

    try { 

        var auth = retrieveToken();

        var journeys = retrieveAllJourneys(auth); 

        Write(Stringify(journeys));

    } catch(error) {

        delete error.jintException;
        error.status = "Error";
        Write(Stringify(error));
    }

    function performGetRequest(url, token) {

        var req = new Script.Util.HttpRequest(url);
            req.emptyContentHandling = 0;
            req.retries = 2;
            req.continueOnError = true;
            req.setHeader("Authorization", "Bearer " + token);
            req.method = "GET";
            req.contentType = "application/json";
            req.encoding = "UTF-8";

        var res = req.send();

        return Platform.Function.ParseJSON(String(res.content));
    }

    function retrieveAllJourneys() {

        var output = [];

        var page = 1;
        var pageSize = 50;

        var requestURL = auth.url + "interaction/v1/interactions?$page=" + page + "&$pageSize=" + pageSize;  

        var result = performGetRequest(requestURL, auth.token)

        if(result.count == 0) return [];

        output = result.items;

        if(result.count > pageSize) {

            var loops = result.count / pageSize;

            for(var i = 1; i < loops; i++) {

                var nextPage = i + 1;

                var requestURL = auth.url + "interaction/v1/interactions?$page=" + nextPage + "&$pageSize=" + pageSize; 

                var result = performGetRequest(requestURL, auth.token)

                var items = result.items;

                for(var k in items) {

                    var item = items[k];

                    output.push(item);

                }

            }

        } 
        return output;
    }

    /* AUTH */

    function retrieveToken() {

        var request = Platform.Function.LookupRows(config.storage.de, "Name", config.storage.name);

        var result = request[0];

        if(result != null && (new Date(config.localDate) < new Date(result.ExpirationDate))) {

            return {
                "token": decryptSymmetric(result.access_token),
                "url": decryptSymmetric(result.rest_instance_url),
                "expires": result.ExpirationDate
            }

        } else {

            var result = requestToken();

            var upsert = storeToken(result);

            if(upsert > 0) {
                return result;
            } else {
                throw "Token not saved"
            }
        }
    }

    function requestToken() {

        var request = HTTP.Post(config.endpoint + "/v2/token", "application/json", Stringify(config.credentials[config.bu]));

        if (request.StatusCode == 200) {

            var result = Platform.Function.ParseJSON(request.Response[0]);

            var parsedDate = new Date(config.localDate);

            var expirationDate = new Date(parsedDate.getTime() + (result.expires_in * 1000));

            return {
                "token": result.access_token,
                "url": result.rest_instance_url,
                "expires": expirationDate
            }

        } else {
            throw "Couldn't request the token. Status: " + request.StatusCode;
        }
    }

    function storeToken(result) {

        var rows = Platform.Function.UpsertData(
            config.storage.de,
            ["Name"], [config.storage.name],
            ["access_token", "rest_instance_url", "ExpirationDate"],
            [encryptSymmetric(result.token), encryptSymmetric(result.url), result.expires]
        );

        if(rows > 0) {
            return rows; 
        } else {
            throw "Token storage failed"
        }

    }

    /* HELPERS */

    function encryptSymmetric(str) {

        Variable.SetValue("@ToEncrypt", str)

        var scr = "";
            scr += "\%\%[";
            scr += "SET @Encrypted = EncryptSymmetric(@ToEncrypt, 'AES', '{{ PASSWORD LABEL }}', @null, '{{ SALT LABEL }}', @null, '{{ VECTOR LABEL }}', @null)";
            scr += "Output(Concat(@Encrypted))";
            scr += "]\%\%";

        return Platform.Function.TreatAsContent(scr);
    }

    function decryptSymmetric(str) {

        Variable.SetValue("@ToDecrypt", str)

        var scr = "";
            scr += "\%\%[";
            scr += "SET @Decrypted = DecryptSymmetric(@ToDecrypt, 'AES', '{{ PASSWORD LABEL }}', @null, '{{ SALT LABEL }}', @null, '{{ VECTOR LABEL }}', @null)";
            scr += "Output(Concat(@Decrypted))";
            scr += "]\%\%";

        return Platform.Function.TreatAsContent(scr);
    }
</script>

Conclusion

This is a common way of looping through the GET requests of the Marketing Cloud REST API, but don’t forget that it’s only applicable to the requests that return paginated results.

Have I missed anything?

Please poke me with a sharp comment below or use the contact form.

  1. Hi, thank you for this great script.
    Would you mind sharing how to adapt this script as an automation to store each journeys as a row in a data extension please ? 🙂 (using the unlimited character field from email studio)

  2. Luke, what do you mean “unlimited character field”? As far as I know, the character limit has always been 4000.

Leave a Reply

Your email address will not be published.

server-side Javascript
Up Next:

How to filter and delete multiple Data Extension records with a Script Activity

How to filter and delete multiple Data Extension records with a Script Activity