A REDCap API call that returns 403 Forbidden is, as far as PROC HTTP is concerned, a completely successful transaction. The request went out, a response came back, and the response body was written to the output fileref exactly as asked. The step ends without an ERROR in the log.
That body just happens to contain an error message instead of your data.
So if you don't check the status code yourself, the next step in your program will happily hand that error text to the JSON libname engine, and you'll spend your time debugging a confusing parse failure rather than reading the plain-English reason your call was rejected.
When PROC HTTP receives a response, it sets two global macro variables:
SYS_PROCHTTP_STATUS_CODE the numeric status - 200, 403, 500 ...
SYS_PROCHTTP_STATUS_PHRASE the accompanying text - "OK", "Forbidden" ...
Note the spelling: PROCHTTP, one word, no underscore between PROC and HTTP.
This trips people up constantly, including me, and including the LLMs I've written SAS with.
SYS_PROC_HTTP_STATUS_CODEreads better and matches the two-word procedure name, so it's what you'll type from memory. It isn't the name SAS uses.
The failure is loud, at least. An unresolved reference in the %IF leaves the literal text behind where a number should be:
WARNING: Apparent symbolic reference SYS_PROC_HTTP_STATUS_CODE not resolved.
ERROR: A character operand was found in the %EVAL function or %IF condition
where a numeric operand is required.
In an interactive session that's obvious. In a batch log it's one error in a program that otherwise appears to keep running — easy to skim past, and your status checking is silently doing nothing.
The status variables describe a response. If PROC HTTP never got one — the host is unreachable, the URL is wrong, a proxy or firewall dropped the connection — there's no status to report.
The trap is that these are ordinary global macro variables, so a value set by an earlier successful call in the same session is still sitting there. A check written against a stale 200 will wave through a call that never actually happened.
Two habits avoid this:
- Clear the variables before the call, so a leftover value can't be mistaken for a fresh one.
- Confirm the variable exists before testing it, with
%SYMEXIST.
/* clear any status left over from an earlier call in this session */
%SYMDEL SYS_PROCHTTP_STATUS_CODE SYS_PROCHTTP_STATUS_PHRASE / NOWARN;
PROC HTTP
URL = "&redcap_url."
METHOD = "POST"
IN = postbody
OUT = apiresp;
HEADERS "Content-Type" = "application/x-www-form-urlencoded";
RUN;
%MACRO check_http_response();
%GLOBAL api_call_ok;
%LET api_call_ok = 0;
/* no status at all means no response came back */
%IF %SYMEXIST(SYS_PROCHTTP_STATUS_CODE) = 0 %THEN %DO;
%PUT ERROR: PROC HTTP returned no HTTP status - the request never reached
the server, or no response was received.;
%RETURN;
%END;
%PUT NOTE: HTTP status=&SYS_PROCHTTP_STATUS_CODE;
%PUT NOTE: HTTP phrase=&SYS_PROCHTTP_STATUS_PHRASE;
%IF &SYS_PROCHTTP_STATUS_CODE >= 200
AND &SYS_PROCHTTP_STATUS_CODE < 300 %THEN %DO;
%LET api_call_ok = 1;
%END;
%ELSE %DO;
%PUT ERROR: HTTP request failed:
&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE;
%END;
%MEND check_http_response;Note that the macro above reports a failure by setting api_call_ok rather than by aborting.
%ABORT only reliably stops a whole submitted program in batch mode. In an interactive session — Display Manager, SAS Studio, Enterprise Guide — %ABORT RETURN merely unwinds the current macro, while %ABORT CANCEL and %ABORT ABEND can tear down the client session entirely. None of that is what you want from a check you intend to reuse.
A flag behaves identically either way: wrap the steps that depend on the call in %IF &api_call_ok EQ 1 %THEN %DO; ... %END; and a failed call simply skips them, leaving the session alone.
basic-api-call-pro.sas puts this together with the rest of a working REDCap export, including dumping the response body when the call fails — worth doing, since REDCap has been observed returning error details as XML even when returnFormat=json was requested.