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.
Dive into keynotes, announcements and breakthroughs on demand.
Explore Now →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.