I am currently struggling to interact with ServiceNOW API. I have been issued a client_id and client_secret which must be protected so I opted to use an authdomain. I have already validated that my id and secret are valid and have acquired a token through Postman. However, I am consistently receiving 401 Unauthorized when trying to utilize PROC HTTP
Program:
filename req temp; filename resp temp; %let SN_INSTANCE = https://xxxxxxxxx.service-now.com; data _null_; file req; put "grant_type=client_credentials"; run; proc http url ="&SN_INSTANCE./oauth_token.do" proxyhost="xxxxxxxxxxxxxxx" proxyport=xxxx method="POST" ct="application/x-www-form-urlencoded" in=req out=resp webauthdomain="SN_API_DEV"; debug level=3 output_text; run;
Hi, this has taken me a while to work out, but I was motivated because we also use ServiceNow here. And I have had some ServiceNow admin training, although it's not part of my day-to-day job.
I was able to get it working and tested by using the ServiceNow developer portal, which gave me access to a dev instance that I could control! I started at: https://developer.servicenow.com/. I have a ServiceNow signin, and I used that to get an instance.
Then I created a user in the SN instance specifically for machine-only API access. Once created, I had to grant that user access to the tables that I wanted to use for the API calls. Simple way is to make the user an admin, but in real life that isn't practical. Here's the user I made. I think making a user for this purpose is easier and more manageable than using your own identity.
Then from the Application Registry screen, I created an OAuth client application. I marked as "Broadly Scoped".
Now I had all necessary data items in hand: client ID, client secret, username, and user password. All of these are needed to get the access token. I created a CSV file with these fields and stored it in a secure folder that only my SAS user account can access. Here's my code to get the access token and then run a simple query, which results in some output from ServiceNow!
/* my dev instance from the developer portal */
%let SN_INSTANCE=https://devNNNNN.service-now.com;
/* Store the credentials in a secure area */
%let credsLoc = /u/&sysuserid./.creds;
/* ===== Build application/x-www-form-urlencoded body ===== */
filename tok temp;
data _null_;
infile "&credsLoc./service-now-creds.csv" dsd firstobs=2;
length client_id $ 40 client_secret $ 40 username $ 40 password $ 250 out $ 500;
length d1-d5 $ 600;
input client_id client_secret username password;
d1 = "grant_type=password";
d2 = catt('client_id=',urlencode(trim(client_id)));
d3 = catt('client_secret=',urlencode(trim(client_secret)));
d4 = catt('username=',urlencode(trim(username)));
d5 = catt('password=',urlencode(trim(password)));
out = catx('&',d1,d2,d3,d4,d5);
;
/* Store API input data in macro var */
call symputx('SN_CRED',out);
put out;
run;
options ls=max;
/* ===== Call token endpoint ===== */
filename tokresp temp;
proc http
url="&SN_INSTANCE./oauth_token.do"
method="POST"
in="&SN_CRED"
out=tokresp
ct="application/x-www-form-urlencoded";
debug level=3 ;
run;
/* ===== Parse JSON and extract access_token ===== */
libname tokjson json fileref=tokresp;
/* token fields often appear in tokjson.root */
data _null_;
set tokjson.root;
if not missing(access_token) then do;
call symputx('accessToken', access_token, 'G');
end;
run;
/* Debugging only */
%put NOTE: Token acquired successfully: &accessToken;
libname tokjson clear;
filename tokresp clear;
/* Query the Incident table with limited parms */
filename results temp;
proc http
url="&SN_INSTANCE./api/now/table/incident?sysparm_fields=number%2Cresolved_by%2Copened_by%2Cshort_description&sysparm_limit=10"
method="GET"
out=results
ct="application/json"
oauth_bearer="&accessToken.";
run;
libname incident json fileref=results;
proc print data=incident.result(obs=5);
var number short_description;
run;
libname incident clear;
filename results clear;
I found the API to be very finicky, especially in getting the token. I might have overengineered, but I needed to URLENCODE all of the values I passed in the HTTP POST call, which I guess is how it's documented -- but I've used a lot of APIs that don't need that step. I first got things working using PowerShell; the Invoke-RestAPI method does a lot for you. Once I got that working, I knew I could do the same in SAS.
WEBAUTHDOMAIN is used for Basic Authentication, but that's not what your client-id and client-secret or token supports.
These values need to go in the HEADERS statement or if it's OAuth, you can use the OAuth_bearer= option for the token value.
You can and should protect these credentials by storing them outside of your program in a file that only you can access. I don't have a ServiceNow example, but here's one from a different service. In my example, the tokens are stored in another SAS program that is in a location that only my account can read. See more in this blog post for guidance.
/* I stored this in hidden folder in my user home with chmod 600 permissions */
%include "/u/&sysuserid./.creds/on24_tokens.sas";
filename resp temp;
proc http
method="GET"
url="https://api.on24.com/v2/client/&&clientId_®ion./event/&eventId."
out=resp
;
headers
"accesstokenkey" = "&&accesstokenkey_®ion."
"accesstokensecret" = "&&accesstokensecret_®ion."
"Accept" = "application/json";
run;
@ChrisHemedinger
Thank you for taking the time to reply! I've read a lot of your posts as I have been lurking in the background
Somehow, I have been able to cobble together something that works to acquire the token. I'm now fighting to pass the token, but that should be the easy part...right? 😉
/* Where we'll store the HTTP response */
filename sn_resp temp;
filename sn_body temp;
filename sn_hdrs temp;
/* Build x-www-form-urlencoded body (URL-encoded HTTP POST body) */
data _null_;
file sn_body lrecl=32767;
put "grant_type=client_credentials";
run;
proc http
url ="&SN_INSTANCE./oauth_token.do"
proxyhost="&PROXY."
proxyport=&PROXY_PORT
method ="POST"
in="grant_type=client_credentials"
out=sn_resp
WEBAUTHDOMAIN="SN_API_DEV";
headers
"Content-Type"="application/x-www-form-urlencoded";
headers "Accept"="application/json";
/* debug level=3 output_text; */
run;
%put &SYS_PROCHTTP_STATUS_CODE;
data _null_;
rc=jsonpp('sn_resp','log');
run;
libname auth json fileref=sn_resp;
data _null_;
set auth.root; /* typically contains access_token, token_type, expires_in, etc. */
call symputx('ACCESS_TOKEN', access_token, 'G');
call symputx('TOKEN_TYPE', token_type, 'G');
call symputx('EXPIRES_IN', expires_in, 'G');
run;
%put NOTE: Got token_type=&TOKEN_TYPE expires_in=&EXPIRES_IN;
%put NOTE: ACCESS TOKEN=&ACCESS_TOKEN;
filename api_out temp;
/* This crap isn't working yet 02/11/2026 */
proc http
url ="&SN_INSTANCE./api/now/table/incident"
proxyhost="&PROXY."
proxyport=&PROXY_PORT
method ="GET"
/* oauth_bearer="&access_token" */
out =api_out;
headers
"Accept" = "application/json"
"Authorization" = "Bearer &ACCESS_TOKEN.";
/* debug level=3 output_text; */
run;
/* Optional: parse the API JSON response */
libname api json fileref=api_out;
proc print data=api.result(obs=5);
run;
Hi, this has taken me a while to work out, but I was motivated because we also use ServiceNow here. And I have had some ServiceNow admin training, although it's not part of my day-to-day job.
I was able to get it working and tested by using the ServiceNow developer portal, which gave me access to a dev instance that I could control! I started at: https://developer.servicenow.com/. I have a ServiceNow signin, and I used that to get an instance.
Then I created a user in the SN instance specifically for machine-only API access. Once created, I had to grant that user access to the tables that I wanted to use for the API calls. Simple way is to make the user an admin, but in real life that isn't practical. Here's the user I made. I think making a user for this purpose is easier and more manageable than using your own identity.
Then from the Application Registry screen, I created an OAuth client application. I marked as "Broadly Scoped".
Now I had all necessary data items in hand: client ID, client secret, username, and user password. All of these are needed to get the access token. I created a CSV file with these fields and stored it in a secure folder that only my SAS user account can access. Here's my code to get the access token and then run a simple query, which results in some output from ServiceNow!
/* my dev instance from the developer portal */
%let SN_INSTANCE=https://devNNNNN.service-now.com;
/* Store the credentials in a secure area */
%let credsLoc = /u/&sysuserid./.creds;
/* ===== Build application/x-www-form-urlencoded body ===== */
filename tok temp;
data _null_;
infile "&credsLoc./service-now-creds.csv" dsd firstobs=2;
length client_id $ 40 client_secret $ 40 username $ 40 password $ 250 out $ 500;
length d1-d5 $ 600;
input client_id client_secret username password;
d1 = "grant_type=password";
d2 = catt('client_id=',urlencode(trim(client_id)));
d3 = catt('client_secret=',urlencode(trim(client_secret)));
d4 = catt('username=',urlencode(trim(username)));
d5 = catt('password=',urlencode(trim(password)));
out = catx('&',d1,d2,d3,d4,d5);
;
/* Store API input data in macro var */
call symputx('SN_CRED',out);
put out;
run;
options ls=max;
/* ===== Call token endpoint ===== */
filename tokresp temp;
proc http
url="&SN_INSTANCE./oauth_token.do"
method="POST"
in="&SN_CRED"
out=tokresp
ct="application/x-www-form-urlencoded";
debug level=3 ;
run;
/* ===== Parse JSON and extract access_token ===== */
libname tokjson json fileref=tokresp;
/* token fields often appear in tokjson.root */
data _null_;
set tokjson.root;
if not missing(access_token) then do;
call symputx('accessToken', access_token, 'G');
end;
run;
/* Debugging only */
%put NOTE: Token acquired successfully: &accessToken;
libname tokjson clear;
filename tokresp clear;
/* Query the Incident table with limited parms */
filename results temp;
proc http
url="&SN_INSTANCE./api/now/table/incident?sysparm_fields=number%2Cresolved_by%2Copened_by%2Cshort_description&sysparm_limit=10"
method="GET"
out=results
ct="application/json"
oauth_bearer="&accessToken.";
run;
libname incident json fileref=results;
proc print data=incident.result(obs=5);
var number short_description;
run;
libname incident clear;
filename results clear;
I found the API to be very finicky, especially in getting the token. I might have overengineered, but I needed to URLENCODE all of the values I passed in the HTTP POST call, which I guess is how it's documented -- but I've used a lot of APIs that don't need that step. I first got things working using PowerShell; the Invoke-RestAPI method does a lot for you. Once I got that working, I knew I could do the same in SAS.
@ChrisHemedinger
Wow, thanks for letting your curiosity getting the best of you!
I have been pestering my ServiceNow tech team about my privileges (amongst other things). It seems to me that my SNOW team has some more work to do, but I'll surely be taking the route that you did and use the developer portal which I had no idea existed. ServiceNow is definitely not my day-to-day job; neither is SAS, but a Mainframer has to build their own tools ;). I appreciate you!
This side quest inspired me to capture the steps in an article for future searchers: How to use SAS to work with ServiceNow APIs
Nearly 200 sessions are now available on demand with the SAS Innovate Digital Pass.
Explore Now →Learn the difference between classical and Bayesian statistical approaches and see a few PROC examples to perform Bayesian analysis in this video.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.