Hi Community!
I am trying to use the GA4 with SAS Enterprise Guide Proc HTTP.
I have created a test application in Google Analytics and I have enabled the APIs & Service to create an OAuth 2.0 Client Id which is set to Desktop.
I have retrieved the client-id, secret-id, built a URL to authorize my account for this application and run the below code:
%let auth_code=xxx;
%let client_id=yyy;
%let client_secret=zzz;
filename pbody temp;
data _null_;
file pbody lrecl=32767;
put "code=&auth_code" @;
put "&" @;
put "client_id=&client_id" @;
put "&" @;
put "client_secret=&client_secret" @;
put "&" @;
put "redirect_uri=urn:ietf:wg:oauth:2.0:oob" @;
put "&" @;
put "grant_type=authorization_code";
run;
/* === 3. Send token request === */
filename tokresp temp;
filename hedresp temp;
proc http
url="https://oauth2.googleapis.com/token"
method="POST"
in=pbody
ct="application/x-www-form-urlencoded"
out=tokresp
headerout=hedresp
headerout_overwrite;
run;
/* === 4. Print Headers === */
data _null_;
infile hedresp lrecl=32767;
input;
put 'HEADER: ' _infile_;
run;
/* === 5. Print Body (raw dump, works for all cases) === */
data _null_;
infile tokresp recfm=n lrecl=32767;
input c $char1. @;
put c $char1. @;
run;
Even though I run the code, I get the following errors:
NOTE: The file PBODY is:
Filename=E:\SAS\Temp\_TD35700_SASBI_\#LN00105,
RECFM=V,LRECL=32767,File Size (bytes)=0,
Last Modified=19Mar2026:17:04:34,
Create Time=19Mar2026:17:04:34
2 The SAS System 16:58 Thursday, March 19, 2026
NOTE: 1 record was written to the file PBODY.
The minimum record length was 269.
The maximum record length was 269.
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
user cpu time 0.00 seconds
system cpu time 0.00 seconds
memory 574.18k
OS Memory 21340.00k
Timestamp 03/19/2026 05:04:34 PM
Step Count 13 Switch Count 0
51
52
53 /* === 3. Send token request === */
54 filename tokresp temp;
55 filename hedresp temp;
56
57 proc http
58 url="https://oauth2.googleapis.com/token"
59 method="POST"
60 in=pbody
61 ct="application/x-www-form-urlencoded"
62 out=tokresp
63 headerout=hedresp
64 headerout_overwrite;
65 run;
NOTE: 400 Bad Request
NOTE: PROCEDURE HTTP used (Total process time):
real time 0.25 seconds
user cpu time 0.00 seconds
system cpu time 0.01 seconds
memory 486.50k
OS Memory 21596.00k
Timestamp 03/19/2026 05:04:34 PM
Step Count 14 Switch Count 3
66
67 /* === 4. Print Headers === */
68 data _null_;
69 infile hedresp lrecl=32767;
70 input;
71 put 'HEADER: ' _infile_;
72 run;
NOTE: The infile HEDRESP is:
Filename=E:\SAS\Temp\_TD35700_SASBI_\#LN00107,
RECFM=V,LRECL=32767,File Size (bytes)=398,
Last Modified=19Mar2026:17:04:34,
Create Time=19Mar2026:17:04:34
HEADER: HTTP/1.1 400 Bad Request
HEADER: Content-Type: application/json; charset=utf-8
HEADER: Vary: X-Origin
3 The SAS System 16:58 Thursday, March 19, 2026
HEADER: Vary: Referer
HEADER: Date: Thu, 19 Mar 2026 15:04:34 GMT
HEADER: Server: scaffolding on HTTPServer2
HEADER: X-XSS-Protection: 0
HEADER: X-Frame-Options: SAMEORIGIN
HEADER: X-Content-Type-Options: nosniff
HEADER: Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
HEADER: Accept-Ranges: none
HEADER: Vary: Origin,Accept-Encoding
HEADER: Transfer-Encoding: chunked
HEADER:
NOTE: 14 records were read from the infile HEDRESP.
The minimum record length was 0.
The maximum record length was 55.
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
user cpu time 0.01 seconds
system cpu time 0.00 seconds
memory 597.40k
OS Memory 21596.00k
Timestamp 03/19/2026 05:04:34 PM
Step Count 15 Switch Count 0
73
74 /* === 5. Print Body (raw dump, works for all cases) === */
75 data _null_;
76 infile tokresp recfm=n lrecl=32767;
77 input c $char1. @;
78 put c $char1. @;
79 run;
NOTE: UNBUFFERED is the default with RECFM=N.
NOTE: The infile TOKRESP is:
Filename=E:\SAS\Temp\_TD35700_SASBI_\#LN00106,
RECFM=N,LRECL=32767,File Size (bytes)=108,
Last Modified=19Mar2026:17:04:34,
Create Time=19Mar2026:17:04:34
{ "error": "unsupported_grant_type", "error_description": "Invalid grant_type: authorization_code\r\n"}
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
user cpu time 0.00 seconds
system cpu time 0.00 seconds
memory 589.09k
OS Memory 21596.00k
Timestamp 03/19/2026 05:04:34 PM
Step Count 16 Switch Count 0
80
81 %LET _CLIENTTASKLABEL=;
82 %LET _CLIENTPROCESSFLOWNAME=;
83 %LET _CLIENTPROJECTPATH=;
84 %LET _CLIENTPROJECTPATHHOST=;
85 %LET _CLIENTPROJECTNAME=;
86 %LET _SASPROGRAMFILE=;
87 %LET _SASPROGRAMFILEHOST=;
4 The SAS System 16:58 Thursday, March 19, 2026
88
89 ;*';*";*/;quit;run;
90 ODS _ALL_ CLOSE;
91
92
93 QUIT; RUN;
94
Does anyone from the community have an idea on what went wrong?
Best Regards,
Vasileios
#googleanalytics #sas #prochttp #GA4
Hey @vfarmak! Looking at your error code, I notice that it is including CRLF. This means what's being sent is not "authorization_code", but "authorization_code\r\n".
Three options to fix it:
1. Fastest: Add recfm=n to your pbody filename statement:
filename pbody temp recfm=n;
If we write this file out both with and without recfm=n, you'll notice that the file without recfm=n has a carriage return:
Whereas the one with recfm=n does not:
This prevents sending CRLF within your payload.
2. Recommended: Use the form option in PROC HTTP to create name-value pairs and automatically URL encode values:
%let auth_code=xxx;
%let client_id=yyy;
%let client_secret=zzz;
filename tokresp temp;
filename hedresp temp;
proc http
url="https://oauth2.googleapis.com/token"
method="POST"
ct="application/x-www-form-urlencoded"
in=form(
"code"="&auth_code"
"client_id"="&client_id"
"redirect_uri"="urn:ietf:wg:oauth:2.0:oob"
"grant_type"="authorization_code"
)
out=tokresp
headerout=hedresp
headerout_overwrite;
run;
3. Less-preferred: write your payload to the in option directly with a macro variable, but that gets a bit harder to read, and you'll still need to URL encode anything that needs it.
%let auth_code=xxx;
%let client_id=yyy;
%let client_secret=zzz;
%let payload=code=&auth_code%nrstr(&)client_id=&client_id%nrstr(&)redirect_uri=urn:ietf:wg:oauth:2.0:oob%nrstr(&)grant_type=authorization_code;
filename tokresp temp;
filename hedresp temp;
proc http
url="https://oauth2.googleapis.com/token"
method="POST"
in="&payload"
ct="application/x-www-form-urlencoded"
out=tokresp
headerout=hedresp
headerout_overwrite;
run;
Thank your @Stu_SAS for your reply.
I tried the 2nd solution and it still gives me HTTP 404:Bad Request.
I have copied and paste the code in a Notepad++ and checked for \r\n characters (basically I confirmed that when I use the let statement in SAS code, no \r\n occurs. Used the solution and produced the below log
1 The SAS System 10:52 Monday, March 23, 2026
1 ;*';*";*/;quit;run;
2 OPTIONS PAGENO=MIN;
3 %LET _CLIENTTASKLABEL='Retrieve Data 3';
4 %LET _CLIENTPROCESSFLOWNAME='Process Flow';
5 %LET _CLIENTPROJECTPATH='C:\Users\Vasilios.Farmakiotis\OneDrive - Generali Hellas Insurance Company SA\Projects\29.
5 ! Google Analytics Cloud\Snoopy\Google Analytics 4.egp';
6 %LET _CLIENTPROJECTPATHHOST='GHATH7777';
7 %LET _CLIENTPROJECTNAME='Google Analytics 4.egp';
8 %LET _SASPROGRAMFILE='';
9 %LET _SASPROGRAMFILEHOST='';
10
11 ODS _ALL_ CLOSE;
12 OPTIONS DEV=SVG;
13 GOPTIONS XPIXELS=0 YPIXELS=0;
14 %macro HTML5AccessibleGraphSupported;
15 %if %_SAS_VERCOMP_FV(9,4,4, 0,0,0) >= 0 %then ACCESSIBLE_GRAPH;
16 %mend;
17 FILENAME EGHTML TEMP;
18 ODS HTML5(ID=EGHTML) FILE=EGHTML
19 OPTIONS(BITMAP_MODE='INLINE')
20 %HTML5AccessibleGraphSupported
21 ENCODING='utf-8'
22 STYLE=HTMLBlue
23 NOGTITLE
24 NOGFOOTNOTE
25 GPATH=&sasworklocation
26 ;
NOTE: Writing HTML5(EGHTML) Body file: EGHTML
27
28 /* Parameteres for proc http */
29 %let auth_code=xxx;
30 %let client_id=yyy;
31 %let client_secret=zzz;
32
33 /* filenames */
34 filename tokresp temp;
35 filename hedresp temp;
36
37 proc http
38 url="https://oauth2.googleapis.com/token"
39 method="POST"
40 ct="application/x-www-form-urlencoded"
41 in=form(
42 "code"="&auth_code"
43 "client_id"="&client_id"
44 "redirect_uri"="urn:ietf:wg:oauth:2.0:oob"
45 "grant_type"="authorization_code"
46 )
47 out=tokresp
48 headerout=hedresp
49 headerout_overwrite;
50 run;
NOTE: 400 Bad Request
NOTE: PROCEDURE HTTP used (Total process time):
real time 0.07 seconds
user cpu time 0.00 seconds
system cpu time 0.00 seconds
2 The SAS System 10:52 Monday, March 23, 2026
memory 461.37k
OS Memory 22108.00k
Timestamp 03/23/2026 11:06:34 AM
Step Count 27 Switch Count 0
51
52 %LET _CLIENTTASKLABEL=;
53 %LET _CLIENTPROCESSFLOWNAME=;
54 %LET _CLIENTPROJECTPATH=;
55 %LET _CLIENTPROJECTPATHHOST=;
56 %LET _CLIENTPROJECTNAME=;
57 %LET _SASPROGRAMFILE=;
58 %LET _SASPROGRAMFILEHOST=;
59
60 ;*';*";*/;quit;run;
61 ODS _ALL_ CLOSE;
62
63
64 QUIT; RUN;
65
I don't know whether it is the credential type or something else (below is the screenshot).
I will try to create another client ID (not a desktop one) to see if this is the case.
Did PROC HTTP write anything to the out file? That can help determine what went wrong in the request.
Hi @Stu_SAS
I am still working on it. I have read that I need to setup a service account (because I will access with proc http the GA4 and ingest the data via the SAS DI Studio / SAS Enterprise Guide - ETL process).
I will ping you when I get some progress. There is a JWT option that needs to be setup with SAS Datastep (9.4M8 installation here).
As soon I get a result, I will let you know.
Hi @Stu_SAS
I had to rework on the following things:
Below you will see the code in Steps.
/* Step 1 */
filename keyfile "C:\Users\Vasilios.Farmakiotis\Documents\Work\GA4\mobilehub-greece-3e8f337fe225.json";
libname keyjson json fileref=keyfile;
data svc;
set keyjson.root;
call symputx("PRIVATE_KEY", private_key);
call symputx("CLIENT_EMAIL", client_email);
call symputx("TOKEN_URI", token_uri);
run;
/* Step 2 */
/* Call Python script to generate token */
options noxwait noxsync;
/* Adjust path as needed */
x "python C:\Users\Vasilios.Farmakiotis\Documents\Work\GA4\get_ga4_token.py";
/* Read token from access_token.txt */
filename tok "C:\Users\Vasilios.Farmakiotis\Documents\Work\GA4\access_token.txt";
data _null_;
infile tok;
input;
call symputx("ACCESS_TOKEN", _infile_);
run;
%put &=ACCESS_TOKEN;
/* Step 3 */
filename ga4req temp;
filename ga4resp temp;
/* Request body */
data _null_;
file ga4req;
put '{';
put ' "property": "properties/999999999",';
put ' "dateRanges": [{"startDate":"7daysAgo","endDate":"today"}],';
put ' "metrics": [{"name":"activeUsers"}],';
put ' "dimensions": [{"name":"country"}]';
put '}';
run;
proc http
method="POST"
url="https://analyticsdata.googleapis.com/v1beta/properties/999999999:runReport" in=ga4req out=ga4resp; headers "Authorization" = "Bearer &ACCESS_TOKEN" "Content-Type" = "application/json"; run; libname ga4 json fileref=ga4resp;
I am still working on it.
I will let you know.
It's your turn to help shape SAS Innovate 2027. Share your expertise and inspire the SAS community.
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.