Introduction 
 In a previous post ( Using PROC HTTP 1: Reviewing the HTTP Protocol) we discussed the basics of the HTTP protocol, and experimented using several cURL (client for URL) examples to obtain data from an API via the internet. While cURL is the de facto standard for working with APIs from the command line, we’re SAS programmers – and, of course, we wantSASto do the work for us! In the Using FILENAME URL to Access Internet Information post, we experimented with FILENAME URL, but noted that PROC HTTPwas significantly more flexible. Let’s explore replicating the cURL code and FILENAME URL examples using PROC HTTP and debugging unexpected results. 
   
 The Basics 
 URIs for API Access 
 Remember that API resources are accessed via a URI, and a URI is made up of several parts: 
   
    
   
  Select any image to see a larger version.     Mobile users: To view the images, select the "Full" version at the bottom of the page.  
   
 Now, you’re probably used to calling that a URL - and so does PROC HTTP. The URI can be specified using theURLparameter. The general PROC HTTP syntax is: 
 PROC HTTP    
      URL="target-URI"    
      < METHOD ="<http-method>" 
 < authentication-type-options > 
 <header-options> 
 <web-server-authentication-options> 
 <proxy-server-connection-options> 
 <other-options> 
 ; 
 < DEBUG  options;> 
 < HEADERS  "HeaderName"="HeaderValue" < "HeaderName-n"="HeaderValue-n" >;> 
   
    
   
  Read the API documentation! While the httpbin.org examples shown here work, the syntax for each API is unique. The documentation will show how to write a URI to get your desired resource, and how to handle authentication.   
   
 Simple GET request 
 cURL code: 
 curl -X GET http://httpbin.org/get
 
 SAS code: 
  /* Simple GET request */
filename resp temp;
proc http
	url="https://httpbin.org/get"
	method="GET"
	out=resp;
run;
/* Review JSON response in the log */
data _null_;
rc = jsonpp('resp', 'LOG');
run;	  
   
 JSON results: 
    
   
 GET request with custom headers 
 cURL code: 
 curl -H "Accept:application/xml \"
   -H "Content-Type:application/json"\
   -H "User-Agent:MySASCode" httpbin.org/xml 
 
 
 
   Header Text   
   Description   
 
 
 Accept:application/xml 
 Describes the type of data the requesting application expects in the return, in this case, XML. 
 
 
 Content-Type:application/json 
 Describes the format of the request being sent, in this case, JSON. 
 
 
 User-Agent:MySASCode 
 Overrides the default description of the browser or agent being used to send the request. 
 
 
 
   
 SAS code: 
  /* GET request with custom headers */
filename resp temp;
proc http
   url="https://httpbin.org/xml"
   method="GET"
   out=resp;
   headers "Accept"="application/xml"
           "Content-Type"="application/json"
           "User-agent"="MySASCode";
run;
/* Review XML response in the log */
data _null_;
   infile resp;
   input;
   put _infile_;
run;	  
   
 XML Response: 
    
   
 POST request 
 cURL code: 
 curl -X POST "https://httpbin.org/post" \
     -H "accept: application/json" \
     -d "My posted text." 
 SAS code: 
 
  /*POST request */
filename input temp;
filename resp temp;
data _null_;
   file input;
   put "My posted text.";
run;
proc http
	url="https://httpbin.org/post"
	method="POST"
	in=input
	out=resp;
run;		  
 
 
   
 
 
 JSON Response: 
 
 
    
 
   
 PUT request 
 cURL code: 
 curl -X PUT "https://httpbin.org/put" \
     -H "accept: application/json" \
     -d "My text update." 
 SAS code: 
 
 /*PUT request */
filename input temp;
filename resp temp;
data _null_;
   file input;
   put "My text update.";
run;
proc http
   url="https://httpbin.org/put"
   method="PUT"
   in=input
   out=resp;
run;	 
 
 
   
 
 
 JSON Response: 
 
 
   
 
 
    
 
 
   
 DELETE request 
 
 cURL code:    
 
 curl -X DELETE "https://httpbin.org/delete?file=badfile.html" \
     -H "accept: application/json" 
 
   
 SAS code: 
 
 
  /* DELETE request */
filename resp temp;
proc http
     url="https://httpbin.org/delete?file=badfile.html"
     method="DELETE"
     out=resp;
     headers "Accept"="application/json";
run;		  
 
 
   
 
 
 JSON Response: 
 
 
   
 
 
    
 
 
   
 
 Authentication with PROC HTTP 
 HTTP Basic - username and password 
 
 cURL code: 
 
 curl -H "Accept:application/xml" \
     -H "Content-Type:application/json" \
     -H "User-Agent:MyCustomAgent" httpbin.org/xml 
 
 SAS code: 
 
 
 /* HTTP Basic - username and password */
filename resp temp;
proc http
   url="https://httpbin.org/basic-auth/MyID/Pa55w0rd"
   method="GET"
   auth_basic
   WebUsername="MyID"
   WebPassword="Pa55w0rd"
   out=resp;
run;	 
 
 
   
 
 
 JSON Response: 
 
 
   
 
 
    
 
   
 Bearer Token 
 
 cURL code: 
 
 curl -X GET "https://httpbin.org/bearer" \
     -H "accept: application/json" \
     -H "Authorization: Bearer gakdfdadfkae213913" 
 
 SAS code: 
 
 
 /* Bearer token*/
filename resp temp;
proc http
   url="https://httpbin.org/bearer"
   method="GET"
   oauth_bearer="gakdfdadfkae213913"
   out=resp;
run;
/* Review response in the log */
data _null_;
   infile resp;
   input;
   put _infile_;
run;
filename resp clear;	 
 
 
   
 
 
 JSON Response: 
 
 
   
 
 
    
 
   
 Proxy Server Authentication 
 
 cURL code: 
 
 curl -x http://proxy.example.com:8080  \
     -U your_proxy_username:your_proxy_password \
     http://example.com/api/data 
 
 SAS code: 
 
 
 /* Proxy Server Authentication */
/* NOTE: The syntax is valid, but this code does not 
   actually run, so no response is shown.            */
filename resp temp;
proc http
   url="http://example.com/api/data"
   method="GET"
   out=resp
   proxyhost="proxy.example.com"
   proxyport=8080
   proxyusername="myProxyID"
   proxypassword="myProxyPassword";
run;	 
 
 Converting FILENAME URL Code 
 Capturing Response Headers 
 
 FILENAME URL code: 
 
 
 /* Capturing the response headers */
filename head temp; 
filename in url "https://httpbin.org/xml" headers=head; 
/* After read from the URL, the headers have been captured */
data _null_;
	infile head;
	input;
	put _infile_;
run;
filename head clear;
filename in clear;	 
 
 
   
 
 
 JSON Response: 
 
 
   
 
 
        
 
 
   
 
 
 PROC HTTP Code: 
 
 
 /* Capturing Response Headers */
filename resp temp;
filename hdrout temp;
proc http
   url="http://httpbin.org/xml"
   method="GET"
   out=resp
   headerout=hdrout;
run;
/* Review response headers in the log */
data _null_;
   infile hdrout;
	input;
	put _infile_;
run;
filename resp clear;
filename hdrout clear;	 
 
 
   
 
 
 JSON Response: 
 
 
   
 
 
    
 
 
   
 
 
 Note the difference in theConnectionsection of the headers: 
 
 
   
 
 
 
 FILENAME URL: the connection is closed after use, freeing up resources, but introducing latency. 
 PROC HTTP: uses a keep-alive to reduce latency by avoiding the overhead of establishing a new connection for each subsequent request. 
 
 
 Real-World Application: World Bank API 
 
   
 
 
 Converted from FILENAME URL code: 
 
 
 /* NOTE: Please run create_work.countries.sas before this code to create work.countries */   /* The FILENAME URL statement was:*/
/*filename in url  'https://api.worldbank.org/v2/country/all/indicator/SP.POP.TOTL?date=2020&format=json&per_page=5000';*/
/* Replaced the FILENAME statement with this code: */
filename in temp;
proc http 
	url='https://api.worldbank.org/v2/country/all/indicator/SP.POP.TOTL?date=2020&format=json&per_page=5000'
	method="GET"
	out=in
   ;
run;
/* The remaining report code remains the same */
filename jmap temp;
libname in json map=jmap automap=replace ;
/* Join the the World Bank data to the SAS table to create the Country_pop_2020 data set */
proc sql;
create table Country_pop_2020 as
select Country
      ,code
      ,value format=comma15. as Population
   from in.root as r
		inner join
		  countries as c
	on r.countryiso3code=c.Code
	order by value desc
;
quit;
/* Don't need the World Bank connection anymore. Clear librefs and filerefs */
libname in clear;
filename in clear;
filename jmap clear;
/* Create the report based on our new table */
title "Countries with Populations >100,000,000";
ods layout gridded width=6.5in columns=2;
ods region;
proc sql;
select * 
   from Country_pop_2020
	where Population >100000000
   order by Population desc
;
quit;
ods region;
proc sgplot data=country_pop_2020;
   vbar Country / response=Population categoryorder = respdesc;
	where Population >100000000;
run;
ods layout end;
title;	 
 
 
   
 
 
 Results: 
 
 
   
 
 
    
 
 
   
 Debugging 
 
 The PROC HTTP DEBUG statement can produce additional information useful for troubleshooting. I generally use LEVEL= to set the debug level: 
 
 
   
 
 
 
 
 
 LEVEL= value 
  Description  
 
 
  1 
  Displays request and response headers in the log. 
 
 
  2 
  Displays level 1 info + request body 
 
 
  3 
  Displays level 2 info + response body 
 
 
 
 
 
   
 
 
 Here are some other useful options: 
 
 
   
 
 
 
 
 
  Debug Option  
   Description   
 
 
 NO_REQUEST_HEADERS 
  Suppresses the request header 
 
 
 NO_RESPONSE_HEADERS 
  Suppresses the response header (for LEVEL >=2) 
 
 
 NO_REQUEST_BODY 
  Suppresses the request body (for LEVEL >=2) 
 
 
 NO_RESPONSE_BODY 
  Suppresses the response body (for LEVEL =3) 
 
 
 OUTPUT_TEXT 
 
  Displays the request and response bodies as text  
 NOTE: Always use this when LEVEL=3 is specified. 
 
 
 
 
 
 
   
 
 Scenario 1: 
 
 SAS code: 
 
 
 /* A GET request for a non-existing resource */
filename resp temp;
proc http
	url="https://httpbin.org/bad_file_name.html"
	method="GET"
	out=resp;
run;
/* Review JSON response in the log */
data _null_;
   rc = jsonpp('resp', 'LOG');
run;	 
 
 
   
 
 
 From the Log 
 
 
 > GET /bad_file_name.html HTTP/1.1
> User-Agent: SAS/9
> Host: httpbin.org
> Accept: */*
> Connection: Keep-Alive
>;
< HTTP/1.1 404 NOT FOUND
  <Date: Wed, 09 Apr 2025 18:05:26 GMT    <Content-Type: text/html
  <Content-Length: 233
  <Connection: keep-alive
  <Server: gunicorn/19.9.0
  <Access-Control-Allow-Origin: *
  <Access-Control-Allow-Credentials: true  <
NOTE: 404 Not Found	 
 
 Scenario 2: 
 
 The SAS code in this scenario initially produced a NOTE: 401 Unauthorized message in the log when first executed. I added DEBUG LEVEL=3 to get more details about why the operation didn’t work. Because I was using DEBUG LEVEL=3, I also specified OUTPUT_TEXT to forestall system instability if the response data was binary. This option directs PROC HTTP to handle both request and response body data as text. 
 
 
 SAS code: 
 
 
 filename input temp;
filename resp temp;
data _null_;
   file input;
   put "*** My text update ***";
run;
proc http
	url="https://httpbin.org/post"
	method="PUT"
	in=input
	out=resp;
	debug level=3 output_text;
run;
filename input clear;
filename resp clear;	 
 
 
   
 
 
 From the Log: 
 
 
 > PUT /post HTTP/1.1
> User-Agent: SAS/9
> Host: httpbin.org
> Accept: */*
> Connection: Keep-Alive
> Content-Length: 24
> Content-Type: application/octet-stream
>
> *** My text update ***
< HTTP/1.1 405 METHOD NOT ALLOWED
< Date: Wed, 09 Apr 2025 15:05:45 GMT
< Content-Type: text/html
< Content-Length: 178
< Connection: keep-alive
< Server: gunicorn/19.9.0
< Allow: OPTIONS, POST
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
< 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
NOTE: 405 Method Not Allowed
	 
 
 
   
 
 
 In the log, I see that I’ve used the wrong URI for my PUT request. 
 
 
 Wrap-up 
 
 
 Holy mackerel! That was a lot of information! PROC HTTP sure is a powerful tool for accessing and acquiring data from the internet, and for working with APIs. You stuck with it this far - so what do you think? What applications might you have for PROC HTTP? I hope you found this information useful. 
 
   Until next time, may the SAS be with you! 
 
 Mark 
 
 
   
   
   
 
 
   On a personal note: I'm retiring from SAS as of April 30, 2025, so, while this is my last blog post as a SAS employee, it won't be my last post - I'll be back! I will be at IASUG, NEBSUG, and MSUG next month. I'm also co-chairing the Hands-On Workshops (HOW) section for SESUG this year (the SESUG 2025 Call for Abstracts currently open - submit a proposal here) Will I see you in September?   Of course, I'll continue to poke around here on SAS Communities - that's a hard habit to break!    PS: Download a ZIP file containing PDF of posts in this series, and the cURL and SAS code. Get it here:https://bit.ly/SASJediPROCHTTP 
 
 
 Other resources: 
 
 
 
 PROC HTTP Documentation 
 SAS example code repository (GitHub) 
 
 
 
 Related posts: 
 
 
 
 Using FILENAME URL to Access Internet Information 
 Using PROC HTTP 1 - Review of the HTTP Protocol 
 
 
 
 Find more articles from SAS Global Enablement and Learning here . 
 
 
 
						
					
					... View more