Scripted REST API in ServiceNow

Scripted REST API in ServiceNow

Scenario

Let's say we are running a ServiceNow instance which is used for a variety of purposes in an organization. And, let's say also, the group and user data on that instance is our source of truth. Therefore, other third-party systems will need to interact with the SN instance to retrieve that data and align it with their system. Perhaps an organization has a SAP cloud instance and is using it for procurement and finance purposes. User and Group data are never written on the SAP side, they need to match at all times, with what is on the SN instance.

We also, do not want the 3rd party system to just make API calls to the instance at anytime and access whichever data it wants. We want to control what is being exposed, and how it is being exposed. For the purposes of this, I will not go into ACL security as its a huge topic. But yes, that is also part of this scenario, both important and useful. For this demo, I will just focus on the scripting logic and calling the newly created API externally, both in postman and the REST API explorer.

What does the 3rd-party need from the ServiceNow instance?

  • A list of all the active groups on the platform
  • The total number of active groups on the platform
  • A list of users within a particular group
  • A list of groups that a particular user is a member of

Part One - Create the Utility Script with Global Functions

When I create Scripted REST APIs in ServiceNow, I want the script to just be an entry point for the outside party to interact with. I do not want huge blocks of code doing various GlideRecord queries. Like any coding/development, always try and modularize the logic so it can be re-used. Loose-coupling, high-cohesion allows the script to be used for a variety of purposes that will save multiple lines of code every time I need a script elsewhere on the platform.

The Above image shows the custom Script Include and the functions that can be called. I made sure these were accessible and could be easily re-used for other purposes on the platform, perhaps in a flow or action, these can easily be called in one line of code to produce the required data.

var userUtilityFunctions = Class.create();
userUtilityFunctions.prototype = {
    initialize: function() {},

    getUserGroups: function(userName) {

        userID = this.getUserSysId(userName);
        var grGroupMember = new GlideRecord("sys_user_grmember");
        grGroupMember.addQuery("user", userID);
        grGroupMember.query();

        var groupNames = [];

        while (grGroupMember.next()) {
            var groupSysId = grGroupMember.getValue("group");
            var grGroup = new GlideRecord("sys_user_group");
            if (grGroup.get(groupSysId)) {
                var groupName = grGroup.getValue("name");
                groupNames.push(groupName);
            }
        }
        return groupNames;
    },

    getGroupUsers: function(groupName) {

        var groupID = this.getGroupSysId(groupName);

        var grGroupMember = new GlideRecord("sys_user_grmember");
        grGroupMember.addQuery("group", groupID);
        grGroupMember.query();

        var userSysIds = [];

        while (grGroupMember.next()) {
            var userSysId = grGroupMember.getValue("user");
            userSysIds.push(userSysId);
        }

        var groupUsers = [];

        var grUser = new GlideRecord("sys_user");
        grUser.addQuery("sys_id", "IN", userSysIds);
        grUser.query();
        while (grUser.next()) {
            var userName = grUser.getValue("name");
            groupUsers.push(userName);
        }
        return groupUsers;
    },

    getUserSysId: function(uName) {

        var userGR = new GlideRecord('sys_user');
        userGR.addQuery('name', uName);
        userGR.query();
        var sysID = 'no user sys_id found';

        if (userGR.next()) {
            sysID = userGR.getValue('sys_id');
            return sysID;
        } else {
            return sysID;
        }

    },

    getGroupSysId: function(gName) {

        var groupGR = new GlideRecord('sys_user_group');
        groupGR.addQuery('name', gName);
        groupGR.query();
        var group_sys_id = 'no group sys_id found';

        if (groupGR.next()) {
            group_sys_id = groupGR.getValue('sys_id');
            return group_sys_id;
        } else {
            return group_sys_id;
        }

    },

    getActiveGroupCount: function() {
        var agg = new GlideAggregate('sys_user_group');
        var total = 0;
        agg.addQuery('active', true);
        agg.addAggregate("COUNT");
        agg.query();

        if (agg.next()) {
            total = agg.getAggregate("COUNT");
        }

        return total;
    },

    getGroupList: function() {
        var ga = new GlideAggregate('sys_user_group');
        ga.addEncodedQuery('active=true');
        ga.addAggregate('GROUP_CONCAT', 'name');
        ga.query();

        var groupList = [];
        while (ga.next()) {
            var groupName = ga.getValue('name');
            groupList.push(groupName);
        }
        return groupList;
    },

    type: 'userUtilityFunctions'
};        

The code above saves me a lot of time when I get to the scripted REST API, as I can just focus on the request and response, I will let the Utility file do the heavy lifting for me to get the GlideRecords and do the queries. I then made sure all of these worked as expected in a background script. You can see from just a couple of lines of code I can get back the data I need:

Output

Part Two - Create the Scripted REST API

Now the functions are working fine on the ServiceNow side, its time to build the API endpoint. There are two parts when you make a scripted REST API, the first is the container, which is your service - Scripted REST Service. Then I can have many resources within that service as shown in the image earlier in the article. For this demonstration I have made one resource called getGroupUsers, which is also the name of the function in the Script Include to keep it simple.

You can see the automatic Base API path that gets generated. This can then be used for testing in Postman or in the REST API explorer built into ServiceNow. The code below shows that at its basic level, the script is really handling two things; a request and a response.

Important on the first line inside the main function there is the request object, which then accesses the queryParams and lastly the 'group_name' is added, this can be whichever name I choose, but its important to make it meaningful so the third party accessing the API knows what is required to be sent in the request in ServiceNow.

In my case, I want the user to simply pass in the name of the group that they want, and they will get all the users of that group returned to them in an array.

 (function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {

    var utils = new userUtilityFunctions();

    var groupName = request.queryParams.group_name;
    if (!groupName) {
        response.setStatus(500);
        response.setBody({
            "msg": "There was no group_name in the query parameters"
        });
    } else {

        var exists = utils.getGroupSysId(groupName);
        if (exists == 'false') {
            response.setStatus(500);
            response.setBody({
                "msg": "The Group you are requesting does not exist, please check spelling or use the group-list API for a list of names"
            });
        } else {
            var userArray = utils.getGroupUsers(groupName);
            var users = {
                "status": "OK",
                "users": userArray
            };
            response.setBody(users);
        }

    }

})(request, response);        

Output using the API Explorer - Related link on the resource

Output in JSON from Postman

So the first line of the Scripted REST resource is important as it defines the query parameter that is needed to interact with the endpoint. I now want to catch errors and handle them gracefully. So if the the group name was misspelt, its blank or the name doesn't exist, I have made sure the appropriate response gets set. I have used the 500 server error code and set a message in the body of the response for the user. The beauty of doing this, is you can define what messages you want to send back in case of an error. Try and think of useful information and guidance you can give the user in the response body.

You can see the logic in the 'if' and 'else' statements to validates the possible scenarios when an error occurs. If after all of those are checked, the array of users are returned in the response body to the requester.

You can see the output in the images, if the correct parameter is queried in the request, the users from the defined group will be returned in an array. This is one example, and have only really operationalized one of the main functions in the Utility Class for my GET resource.

This is enough to get started, from here, there are more functions that can be used in other scripted resources under this single service of users and groups. There are also some ACLs to configure, along with how all the new REST endpoints support each other for third-parties to quickly and easily get the data they need.

Thanks so much for reading!

Priyanka Salunke

ServiceNow Enthusiast| CSA | CAD |ServiceNow Community Rising Star 2023-24

9 个月

Well explained. Thanks for sharing.

Amit Gujarathi

ServiceNow Architect & Mentor | 4x MVP (2022-24) | Author of "Think Thrice Code Once" | Certified CTA, CIS, CAD, CSA | ITSM & Agile Expert | Empowering Professionals Through Technomonk

1 年

Great article! Scripted REST API in ServiceNow allows for customization and flexibility in presenting data to third parties. Thanks for sharing!

要查看或添加评论,请登录

Paul Stuart的更多文章

  • Implementing HRSD in ServiceNow

    Implementing HRSD in ServiceNow

    The Human Resources Management Problem Human resource management in many organisations face the same issue of time…

    1 条评论
  • Governance, Risk, & Compliance ServiceNow

    Governance, Risk, & Compliance ServiceNow

    Imagine, if you will, a small child with a toy box in their room. For those of you that have raised or looked after…

    2 条评论
  • ITIL 4 Concepts

    ITIL 4 Concepts

    ITIL, or Information Technology Infrastructure Library, is a well-known set of IT best practices designed to assist…

  • ?? Recreate printf() in C

    ?? Recreate printf() in C

    This article will be a full break down of a project I undertook at the 42 Adelaide coding school. It is a re-build of…

    2 条评论
  • Software Asset Management in ServiceNow ??

    Software Asset Management in ServiceNow ??

    Company asset management that is physical and obvious will get looked after and prioritized especially if it is vital…

    8 条评论
  • Identification & Reconciliation Discovery in ServiceNow

    Identification & Reconciliation Discovery in ServiceNow

    If we were to think of a football game, we may use a variety of techniques and strategies to try and kick the ball in…

    2 条评论
  • Discovery Fundamentals

    Discovery Fundamentals

    Challenges for an organizations ERP system includes identifying all the most important devices, assets, and critical…

    9 条评论
  • Table Mapping from External API

    Table Mapping from External API

    Normally in ServiceNow, often the standard way to handle an external data source writing to tables in the instance, is…

    2 条评论
  • Glide Ajax - ServiceNow

    Glide Ajax - ServiceNow

    Article accompanying video on how to use Glide Ajax in ServiceNow. In my last post I demonstrated how Glide Ajax can be…

    1 条评论

其他会员也浏览了