4/5/19: This article has been updated on 4/5/19 to reflect changes to the platform, add more screenshots and clarity, and to add a link to the free, downloadable version of this tool.
11/25/19: This article has been updated to improve the code and handle some edge cases. The tool on the download page has also been updated to version 2.0.
In Fuji and prior releases, ServiceNow has supported the REST API, but they've only supported simple table and record APIs using GET, PUT, POST, PATCH, and so on. In Geneva however, ServiceNow introduced Scripted REST Apis.
Scripted REST APIs allow a developer to create a custom REST API Resource, receive request arguments via a REST call, and return data (as with GET requests), create a record (as with POST requests), update records (as with PUT/PATCH requests), or delete records (using a - you guessed it - DELETE request) using custom code that can utilize REST headers, query parameters, and/or request body elements.
Scripted REST APIs have only become available fairly recently (in the Geneva release of ServiceNow), and are something that a lot of developers have been avoiding, as they're new and arcane, and the documentation around them is still fairly new, and not very well built-out.
In this article, we're going to learn how to create our own custom scripted REST API, which we'll use for our demo project: Retrieving the service catalog variables associated with a given RITM, for reporting in an external system.
Pro Tip: If you came here just wanting to deploy this functionality, skip to the bottom of this article, where you'll find information on obtaining a pre-built update set that you can deploy into your instance to enable retrieving catalog item variables via REST!
Before we get started, I want to quickly make clear what is, and is not, available in ServiceNow by default.
If you've only upgraded to Geneva, there are no scripted REST APIs available by default (except maybe a "user role inheritance" internal API), so you'll pretty much have to write each one yourself.
However, if you're on Helsinki, there is a "Service Catalog REST API", which allows you to do things like add a catalog item to a cart, update a cart, delete a catalog item from a cart, submit a cart, submit a record producer, and loads more. This is not mentioned in any of the documentation that I've found, and I only learned about it by exploring in my developer instance while writing this article! So if you are just trying to submit catalog items via REST, now you know! There's a way (and a very robust way at that) to do it in Helsinki.
One thing that's missing, even from the Helsinki scripted REST APIs however, is what we're going to build today -- A simple way to retrieve service catalog variables associated with a given RITM.
What is REST?
To understand scripted REST APIs, we have to first understand what REST is, and how it's used in ServiceNow.
I'm not going to go into great depth here about REST, but suffice it to say that REST is a stateless, cacheable "language" that clients and servers can use to talk to one another, and pass data back and forth. You can send a request to a REST resource via an API. REST requests have a few key components:
Resource URI: This is the "Unique Resource Identifier". It usually begins with "HTTP://", and it looks like a web address. In fact, it is a web address! If you were to take a simple rest GET request for example, and put it into your browser's URL bar, you would get the results of that request in your browser window!
Request Header: This is a set of key-value pairs attached to the request, and is not unlike the header on a webpage. It contains some metadata about the request, such as an authorization token, what "language" (Content-Type) the request is in, and what Content-Type the request will accept a response in.
Request Body: The Body of the request is where the bulk of the data can be found. If you're sending a "POST" request for example, which might generate a record on a server, the body of the request might contain the contents of the record to be created.
Query Parameters: Query parameters are values added onto the URI of a webpage or, in this case, a REST call. A parameter would look like this:
http://site.com/api/v1/resource?sys_id=D-1
Similar to how variables work in javascript, request headers, parameters, and the request body can be used to pass data between the client and server. There are a few simple tools that you can use to build REST requests. One of my favorite, is called PostMan for Chrome, and it is free.
Let's Get Started!
The first thing that we have to do to get started, is navigate to System Web Services -> Scripted Web Services -> Scripted REST APIs, from the Application Navigator.
Right away, you'll notice some significant differences, depending on whether you're in Geneva, or Helsinki or later. In this article, I'm going to be focusing on the Geneva functionality, because it requires more manual work. It should be almost the same, but a smidge easier, in Helsinki. The code will be the same.
Create the SRAPI
While Helsinki has some Scripted REST APIs already in place, in Geneva, you should be presented with an essentially empty list.
To begin the creation of your REST API, click on New, and fill out a name and API ID. I'm going to use ritm_vars_api (Same as my API name) but you can call yours whatever you like. The API ID will identify your API specifically, and will be part of the API URI. Your API URI will be something like this, when we're through:
http://your_instance.service-now.com/api/your_company_identifier/API_ID.
Save your new Scripted REST Service record, and you'll have 3 new tabs, and 3 related lists. The related lists represent REST resources, headers, and query parameters (described above).
As we build a scripted API to send over the variables and variable values associated with a given request item, we'll need to create a way for the requestor to pass in the RITM ticket numbers they'd like to retrieve the variables for.
We could use query parameters, but let's make use of a request header instead. That just feels more formal to me; you can build your API however you like, so long as you document it!
Create the REST header
To create a request header, simply select the related list, and click New. The header name is like a variable value, and the user will populate it with the RITM numbers of the tickets they'd like to get the variables for. I created one that looks like this:
As you can see from the example value, I'm allowing the user to pass in multiple ticket numbers, or just one. The list should be comma-separated. This will make it easier on the requestor if they're generating the API call from a script, because they can just pass an array into the header value and javascript will coerce it into a comma-delimited string in order to conform to the application/json content-type.
And that's pretty much all the data we need from the user for this Scripted REST Service - so let's define our resource.
Create the REST resource
Back on the Scripted REST Service page, open up the Resources related list, and click New. On this page, we need to define the Http method (get, post, put, etc.), the resource name, and the script that'll do the work for us.
For the name I entered Get RITM Variables. For the Http Method, I selected GET. In the Documentation section, I entered "This resource returns the variables related to the RITM you've specified.".
Associate the header to the resource
Now that the Request Header has been created on the Scripted REST API record, and now that we’ve created the Scripted REST Resource record, we need to make sure that the Request Header is associated to the REST Resource as well. This is done from the Request Header Associations related list.
If you don’t already see it here, create a new record that associated the “ritm” Request Header, to the REST resource you created in the previous step.
Write the script
Now all we have to do is define a script which will get the request header, and define the response so that it contains the results that the requestor is looking for. This is the scripted part of Scripted REST APIs. When you create a new resource, you'll be given a script stub that looks something like this:
(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) { // implement resource here })(request, response);
From this stub, we can tell that we've got access to two parameters that we can make use of, inside the process function: request (a RESTAPIRequest object), and response (a RESTAPIResponse object). If you read our article, What's New in Geneva/UI16, you know that the Geneva script editor is far more powerful now, than it used to be. We can make use of the intellisense style code-completion, to see what methods of the RESTAPIRequest and RESTAPIResponse objects we have access to, by typing "request." or "response." like so:
From this, we can learn a few things about how this all works -- for example, we can use request.getHeader('ritm') to get the value of the RITM header that the user sent along with their request!
Here's my code for processing these requests:
(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) { var variableName, i, ritmVariables, ritmNumber, grRitm, variablesObject, ritmNumbers; var responseObj = {}; //The object that should ultimately be returned. //Set ritmNumber from header value, convert to an array, and trim each element. ritmNumbers = trimAndSplit(request.getHeader('ritm'), ','); //Calling custom script include. //ritmNumbers = request.getHeader('ritm').split(','); //<--Commented out, but you can enable it if you don't have the trimAndSplit script include. if (!ritmNumbers) { //If ritm header was not set or was malformed, return an error. response.setStatus(417); return 'Unable to process request. Request header \'ritm\' not defined. ' + 'Please define a request header called \'ritm\' with the value set to the ticket number for the RITM you\'d like to retrieve the variables for. Value provided for RITM: ' + ritmNumbers; } grRitm = new GlideRecord('sc_req_item'); //For each RITM in the ritmNumbers array... for (i = 0; i < ritmNumbers.length; i++) { //Get the RITM number ritmNumber = ritmNumbers[i]; //Get the GlideRecord for the RITM, or throw an error if we can't. if (!grRitm.get('number', ritmNumber)) { //If we can't find the RITM... response.setStatus(417); //set the status to indicate a bad argument response.setHeader(ritmNumber, 'Unable to locate requested ritm: ' + ritmNumber); //add a header to indicate which RITM was bad. gs.logError( 'Scripted REST API ritm_vars_api unable to process request. RITM not found: ' + ritmNumber); //Log an error so the admin knows what happens if the requestor asks. continue; //Continue the loop with the next RITM in case there's anything valid we *can* return. } //Get the object containing the catalog variables. ritmVariables = grRitm.variables; //Declare a fresh object each loop. //This will be made to contain all of the variables and variable values for the RITM we're iterating over, //Then it will be pushed into the responseObj object, and reset for the next RITM on the next iteration. variablesObject = {}; //Set the 'number' property on the variables object to the current ritm number. This will also be the variablesObject["number"] = ritmNumber; //Iterate over ritmVariables, looping through each one as v. //This is necessary because the "gr.variables" is not a standard JS object, and cannot be mapped. for (variableName in ritmVariables) { //NOTE: If you want to return all variables on the catalog item associated with the RITM, remove the second condition in the IF block below. //With the second condition, this will only show variables that have been populated. if (ritmVariables.hasOwnProperty(variableName) && ritmVariables[variableName]) { //Make sure the property exists and isn't null or unknown. variableName = variableName.toString(); //Make sure we're all proper strings here. //pushing the variable into variablesObject, which will be copied into responseObject along with a version of variablesObject for each of the RITMs. variablesObject[variableName] = ritmVariables[variableName].toString(); } } //Call addObjToObj function to add variablesObject to responseObj with the key set to the current RITM number, so it can be accessed as 'responseObj["RITM0123456"]'. //NOTE: If we didn't use addObjToObj, we'd run into this problem where objects are linked rather than copied, when added to other objects in javascript. responseObj = addObjToObj(responseObj, variablesObject, ritmNumber); } return responseObj; //Returning the variables and their values. Returning an object here will set the response "body" to the JSON equivalent of this object. //Helper function below function addObjToObj(parent, child, name) { parent[name] = child; return parent; //Note: Does not break pass-by-reference, because we're declaring a new object on each loop on line 39 above. } })(request, response);
You can read through the code comments to get a really good sense of what's going on, but basically this script just grabs the value associated with the request header from the request that triggered the REST service to run, then queries the database for the values of the variables associated with the RITMs listed in that header. Once it has them, it returns all the variables and values that have been populated as an object which, in JSON, is returned as the body of the response back to the requestor. Simple!
Add the Script Include
On line 6 in my REST resource, I make use of a custom script include that I wrote called trimAndSplit(), so here's the code for that as well:
/** * Converts a string to an array, much like .split(), except that it trims each element of leading and trailing whitespace. * @param inputString {string} The string that should be split and trimmed * @param [token=,] {string} The character to split on (usually ','). Default value is a comma character. * @returns {Array} An array of elements that have each been trimmed of whitespace. */ function trimAndSplit(inputString, token) { var i, inputArr; var resultArray = []; //Give token a default value if not specified token = (!token) ? ',' : token; if (!inputString || typeof inputString !== 'string') { gs.logError( 'trimAndSplit function received an invalid argument in the first argument. The argument received was: ' + inputString + '. The only acceptable first argument for trimAndSplit, is a string.'); return []; } if (inputString.indexOf(token) < 0) { //Return the original string in an array, because no instances of the token were found. return [inputString]; } //convert the input string to an array, splitting on the provided token (usually ',') inputArr = inputString.split(token); for (i = 0; i < inputArr.length; i++) { //Trim each element in the split string array, then push it to the return array. resultArray.push(inputArr[i].trim()); } //return the trimmed and split array. return resultArray; }
And that's about all there is to it! You can access this API using a tool like the one I mentioned near the beginning of this article, by using a request like this:
Pro Tip: In the URI next to GET in the above screenshot, the "my_company" bit can be replaced with the API namespace value from your Scripted REST Service page.
If you were to run a request like the one above against an instance that had this scripted REST API enabled, you would get a response that looked something like this:
{ "result": { "RITM0087924": { "phone": "123456", "short_description": "Mobile App Deployment", "email": "fgj@fgj.com", "request_type": "Mobile App Deployment", "description": "test", "requested_by": "0b7bb7840a0a3cd10145e7471b1dbe57", "number": "RITM0087924", "vp": "0b5672ce0a0a3cd0011356150042b37a" }, "RITM0087923": { "phone": "5122122155", "short_description": "Mobile App Deployment", "email": "gul@ghm.com", "request_type": "Mobile App Deployment", "description": "test by ghm", "project_id": "146bd15bhf01ddc0e00334afae3ee4e2", "requested_by": "fc46dfhh6fedda805d0be981be3ee43e", "number": "RITM0087923", "vp": "0b5h72ce0a0a3cd1011356150042b37a", "requested_for": "0b7bb7940hha3cd10145e7471b1dbe57" } } }
You can download this tool for free, at the link below, where you’ll also find documentation and usage instructions.
Download
Thanks for reading! We put a lot of work into these articles, so if you liked it, we humbly welcome you to subscribe to see more. If your company needs help implementing, enhancing, or building new ServiceNow solutions, we also humbly welcome you to get in touch with us to discuss how we might be able to help you out!
- March 2024
-
February 2024
- Feb 12, 2024 5 Lessons About Programming From Richard Feynman
- July 2023
- May 2023
- April 2023
-
December 2022
- Dec 13, 2022 ServiceNow Developers: BE THE GUIDE!
- October 2022
-
August 2022
- Aug 23, 2022 Using .addJoinQuery() & How to Query Records with Attachments in ServiceNow
- Aug 18, 2022 Free, Simple URL Shortener for ServiceNow Nerds (snc.guru)
- Aug 16, 2022 How to Get and Parse ServiceNow Journal Entries as Strings/HTML
- Aug 14, 2022 New tool: Get Latest Version of ServiceNow Docs Page
- March 2022
- February 2022
- May 2021
- April 2021
- February 2021
-
November 2020
- Nov 17, 2020 SN Guys is now part of Jahnel Group!
- September 2020
- July 2020
-
January 2020
- Jan 20, 2020 Getting Help from the ServiceNow Community
- December 2019
- November 2019
-
April 2019
- Apr 21, 2019 Understanding Attachments in ServiceNow
- Apr 10, 2019 Using Custom Search Engines in Chrome to Quickly Navigate ServiceNow
- Apr 4, 2019 Set Catalog Variables from URL Params (Free tool)
- Apr 1, 2019 Outlook for Android Breaks Email Approvals (+Solution)
- March 2019
-
February 2019
- Feb 27, 2019 Making Update Sets Smarter - Free Tool
-
November 2018
- Nov 29, 2018 How to Learn ServiceNow
- Nov 6, 2018 ServiceNow & ITSM as a Career?
- October 2018
- September 2018
-
July 2018
- Jul 23, 2018 Admin Duty Separation with a Single Account
-
June 2018
- Jun 19, 2018 Improving Performance on Older Instances with Table Rotation
- Jun 4, 2018 New Free Tool: Login Link Generator
-
May 2018
- May 29, 2018 Learning ServiceNow: Second Edition!
- April 2018
- March 2018
-
February 2018
- Feb 11, 2018 We have a new book!
- November 2017
-
September 2017
- Sep 12, 2017 Handling TimeZones in ServiceNow (TimeZoneUtil)
- July 2017
-
June 2017
- Jun 25, 2017 What's New in ServiceNow: Jakarta (Pt. 1)
- Jun 4, 2017 Powerful Scripted Text Search in ServiceNow
- May 2017
- April 2017
-
March 2017
- Mar 12, 2017 reCAPTCHA in ServiceNow CMS/Service Portal
-
December 2016
- Dec 20, 2016 Pro Tip: Use updateMultiple() for Maximum Efficiency!
- Dec 2, 2016 We're Writing a Book!
-
November 2016
- Nov 10, 2016 Chrome Extension: Load in ServiceNow Frame
- September 2016
-
July 2016
- Jul 17, 2016 Granting Temporary Roles/Groups in ServiceNow
- Jul 15, 2016 Scripted REST APIs & Retrieving RITM Variables via SRAPI
-
May 2016
- May 17, 2016 What's New in Helsinki?
-
April 2016
- Apr 27, 2016 Customizing UI16 Through CSS and System Properties
- Apr 5, 2016 ServiceNow Versions: Express Vs. Enterprise
-
March 2016
- Mar 28, 2016 Update Set Collision Avoidance Tool: V2
- Mar 18, 2016 ServiceNow: What's New in Geneva & UI16 (Pt. 2)
-
February 2016
- Feb 22, 2016 Reference Field Auto-Complete Attributes
- Feb 6, 2016 GlideRecord & GlideAjax: Client-Side Vs. Server-Side
- Feb 1, 2016 Make Your Log Entries Easier to Find
-
January 2016
- Jan 29, 2016 A Better, One-Click Approval
- Jan 25, 2016 Quickly Move Changes Between Update Sets
- Jan 20, 2016 Customize the Reference Icon Pop-up
- Jan 7, 2016 ServiceNow: Geneva & UI16 - What's new
- Jan 4, 2016 Detect/Prevent Update Set Conflicts Before They Happen
-
December 2015
- Dec 28, 2015 SN101: Boolean logic and ServiceNow's Condition Builder
- Dec 17, 2015 Locate any record in any table, by sys_id in ServiceNow
- Dec 16, 2015 Detecting Duplicate Records with GlideAggregate
- Dec 11, 2015 Array.indexOf() not working in ServiceNow - Solution!
- Dec 2, 2015 Understanding Dynamic Filters & Checking a Record Against a Filter Using GlideFilter
- October 2015
-
August 2015
- Aug 27, 2015 Easily Clone One User's Access to Another User