Script.Util.HttpRequest is the most flexible HTTP client available in SSJS. It supports all HTTP methods, custom headers, timeouts, and gives you full access to response status codes, headers, and body.

Script.Util.HttpRequest does not require Platform.Load. It is available in all SSJS contexts.

Syntax

var req = new Script.Util.HttpRequest(url);
req.method = "GET";                         // HTTP method
req.contentType = "application/json";       // Content-Type for body
req.encoding = "UTF-8";                     // Encoding (default Windows-1252)
req.timeout = 30;                           // Timeout in seconds
req.setHeader(name, value);                 // Set a request header
req.postData = body;                        // Request body (POST/PUT/PATCH) — write-only
req.emptyContentHandling = 0;               // 0 = continue, 1 = stop, 2 = next subscriber
req.retries = 2;                            // Number of retries on failure
req.continueOnError = true;                 // If true, don't throw on HTTP error status
var resp = req.send();
Show test script
<script runat="server">
/*
 * Chapter: Syntax
 *
 * Proves:
 *   1. `new Script.Util.HttpRequest(url)` works WITHOUT Platform.Load.
 *   2. The constructor returns an HttpRequestInstance (a CLR proxy).
 *   3. Every property named in the Syntax snippet is assignable, and every
 *      method named in it exists on the instance.
 *
 * EXPECTED OUTPUT: every line starts with PASS.
 */

function assert(id, actual, expected) {
    var got;
    try { got = "" + actual; } catch (ex) { got = "THREW: " + ("" + ex.message); }
    Platform.Response.Write((got === "" + expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}

/* 1. Constructor works with no Platform.Load call anywhere above it. */
var req = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");
assert("constructor returns an object (CLR proxy)", typeof req, "clr");

/* 2. Every property from the Syntax snippet is assignable. */
req.method = "GET";
assert("method is assignable", req.method, "GET");
req.contentType = "application/json";
assert("contentType is assignable", req.contentType, "application/json");
req.encoding = "UTF-8";
assert("encoding is assignable", req.encoding, "utf-8");
req.timeout = 30;
assert("timeout is assignable", req.timeout, "30");
req.emptyContentHandling = 0;
assert("emptyContentHandling is assignable", req.emptyContentHandling, "0");
req.retries = 2;
assert("retries is assignable", req.retries, "2");
req.continueOnError = true;
assert("continueOnError is assignable", req.continueOnError, "True");

/* 3. postData is assignable — the assignment itself never throws. */
var assigned = "ok";
try { req.postData = "body"; } catch (ex) { assigned = "THREW: " + ("" + ex.message); }
assert("postData is assignable", assigned, "ok");

/* 4. Every method from the Syntax snippet exists. */
assert("setHeader() exists", typeof req.setHeader, "clrmethodinfo");
assert("send() exists", typeof req.send, "clrmethodinfo");
</script>

Parameters

Name Type Required Description
url string Yes Destination URL
Show test script
<script runat="server">
/*
 * Chapter: Parameters
 *
 * Proves:
 *   1. `url` is required — the documented single-argument call works.
 *   2. `url` is a string parameter: a valid URL string is accepted and the
 *      resulting instance sends to that URL.
 *   3. An empty URL is NOT a working call form — it throws.
 *
 * EXPECTED OUTPUT: every line starts with PASS.
 */

function assert(id, actual, expected) {
    var got;
    try { got = "" + actual; } catch (ex) { got = "THREW: " + ("" + ex.message); }
    Platform.Response.Write((got === "" + expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
    var threw = false, msg = "";
    try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
    Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}

/* 1. The documented one-argument string call builds a usable instance. */
var req = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");
assert("url string accepted, instance created", typeof req, "clr");

/* 2. The instance actually targets that URL — send() returns 200. */
var resp = req.send();
assert("send() reached the given url (statusCode 200)", resp.statusCode, "200");

/* 3. An empty url is not a usable call form. */
assertThrows("empty url is not usable", function () {
    var bad = new Script.Util.HttpRequest("");
    return bad.send();
});
</script>

HttpRequestInstance Properties

The req object returned by Script.Util.HttpRequest(url) has these properties you can set to configure the request:

Property Type Default Description
method "GET","POST","PUT","PATCH","DELETE" "GET" HTTP method
contentType string "" Content-Type header for body, e.g. "application/json"
encoding string "Windows-1252" Character encoding. Set it explicitly to "UTF-8" when the body is UTF-8 — an assigned value reads back lower-cased ("utf-8")
timeout number 30 Request timeout in seconds
postData string (write-only) Request body (for POST/PUT/PATCH). Assignment works, but reading the property throws
emptyContentHandling number 0 Indicates what to do if the request doesn’t return any content. 0 = continue, 1 = stop the request, 2 = continue to the next subscriber (only works in email sends)
retries number 1 The number of times to retry the request before throwing an exception
continueOnError boolean false If true, continues after receiving a non-fatal error; if false, throws an exception

req.postData is write-only. Reading it — even right after assigning it — throws Property Get method was not found., and outside a try/catch that throw aborts the entire CloudPage. Keep the body in your own variable if you need it again.

Show test script
<script runat="server">
/*
 * Chapter: HttpRequestInstance Properties
 *
 * Proves, for each documented property, its DEFAULT value and whether it is
 * writable:
 *   1. method                default "GET", writable, accepts POST/PUT/PATCH/DELETE
 *   2. contentType           default "" (empty), writable
 *   3. encoding              writable; an assigned value reads back LOWER-CASED
 *   4. timeout               default 30, writable
 *   5. emptyContentHandling  default 0, accepts 1 and 2
 *   6. retries               default 1, writable
 *   7. continueOnError       default false, writable
 *
 *   DEVIATIONS from the official Salesforce docs, each marked "DEV":
 *     - encoding defaults to "Windows-1252", not the documented "UTF-8".
 *     - postData is WRITE-ONLY: every read throws "Property Get method was
 *       not found.", although the docs list it as a readable property.
 *     - timeout is not documented at all; it exists and defaults to 30,
 *       matching the documented 30-second send() timeout, so the unit is
 *       SECONDS — not milliseconds.
 *     - emptyContentHandling is documented as a boolean but is numeric at
 *       runtime: true/false are rejected outright.
 *
 * EXPECTED OUTPUT: every line starts with PASS.
 */

function assert(id, actual, expected) {
    var got;
    try { got = "" + actual; } catch (ex) { got = "THREW: " + ("" + ex.message); }
    Platform.Response.Write((got === "" + expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrowsFragment(id, fn, fragment) {
    var msg = "";
    try { fn(); msg = "did NOT throw"; } catch (ex) { msg = "" + ex.message; }
    Platform.Response.Write((msg.indexOf(fragment) > -1 ? "PASS " : "FAIL ") + id + " -> " + msg + "\n");
}

var req = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");

/* 1. method — default and writability. */
assert("method default is GET", req.method, "GET");
req.method = "POST";
assert("method accepts POST", req.method, "POST");
req.method = "PUT";
assert("method accepts PUT", req.method, "PUT");
req.method = "PATCH";
assert("method accepts PATCH", req.method, "PATCH");
req.method = "DELETE";
assert("method accepts DELETE", req.method, "DELETE");
req.method = "GET";

/* 2. contentType — default empty, writable. */
var fresh = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");
assert("contentType default is empty", fresh.contentType, "");
fresh.contentType = "application/json";
assert("contentType is writable", fresh.contentType, "application/json");

/* 3. DEVIATION — encoding default is Windows-1252, not the documented UTF-8. */
var encReq = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");
assert("DEV encoding default is Windows-1252 (docs: UTF-8)", encReq.encoding, "Windows-1252");
encReq.encoding = "UTF-8";
assert("DEV assigned encoding reads back lower-cased (docs: UTF-8)", encReq.encoding, "utf-8");

/* 4. DEVIATION — timeout is undocumented; default 30 proves the unit is seconds. */
var toReq = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");
assert("DEV timeout default is 30 seconds (docs: property not listed)", toReq.timeout, "30");
toReq.timeout = 45;
assert("timeout is writable", toReq.timeout, "45");

/* 5. DEVIATION — postData is write-only: assignment works, every read throws. */
var pdReq = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");
assertThrowsFragment("DEV reading postData before assignment throws (docs: readable property)", function () {
    return pdReq.postData;
}, "Property Get method was not found.");
pdReq.postData = "hello-body";
assertThrowsFragment("DEV reading postData after assignment still throws (docs: readable property)", function () {
    return pdReq.postData;
}, "Property Get method was not found.");

/* 6. DEVIATION — emptyContentHandling is numeric, not the documented boolean. */
var ecReq = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");
assert("emptyContentHandling default is 0", ecReq.emptyContentHandling, "0");
ecReq.emptyContentHandling = 1;
assert("emptyContentHandling accepts 1 (stop)", ecReq.emptyContentHandling, "1");
ecReq.emptyContentHandling = 2;
assert("emptyContentHandling accepts 2 (next subscriber)", ecReq.emptyContentHandling, "2");
assertThrowsFragment("DEV emptyContentHandling rejects true (docs: boolean)", function () {
    ecReq.emptyContentHandling = true;
}, "cannot be converted");
assertThrowsFragment("DEV emptyContentHandling rejects false (docs: boolean)", function () {
    ecReq.emptyContentHandling = false;
}, "cannot be converted");

/* 7. retries and continueOnError — defaults and writability. */
var rReq = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");
assert("retries default is 1", rReq.retries, "1");
rReq.retries = 2;
assert("retries is writable", rReq.retries, "2");
assert("continueOnError default is false", rReq.continueOnError, "False");
rReq.continueOnError = true;
assert("continueOnError is writable", rReq.continueOnError, "True");
</script>

HttpRequestInstance Methods

The req object returned by Script.Util.HttpRequest(url) has these methods you can call to configure the request:

Method Returns Description
clearHeaders() void Remove all custom headers
removeHeader(name) void Remove a specific header by name
send() HttpResponseInstance Send the request
setHeader(name, value) void Set a custom request header

clearHeaders

Removes all custom headers previously set on the request.

var req = new Script.Util.HttpRequest("https://api.example.com/data");
req.method = "GET";
req.setHeader("Authorization", "Bearer " + token);
req.clearHeaders(); // removes Authorization and all other custom headers
var resp = req.send();

removeHeader

Removes a specific header from the request by name.

var req = new Script.Util.HttpRequest("https://api.example.com/data");
req.method = "GET";
req.setHeader("Authorization", "Bearer " + token);
req.setHeader("X-Debug", "1");
req.removeHeader("X-Debug");
var resp = req.send();

send

Sends the request and returns an HttpResponseInstance. May throw on connection failure or timeout — wrap in try/catch for production code.

try {
    var resp = req.send();
} catch (e) {
    Write("Request failed: " + e.message);
}

setHeader

Adds or replaces a header on the outgoing request. Call it once per header.

var req = new Script.Util.HttpRequest("https://api.example.com/data");
req.method = "GET";
req.setHeader("Authorization", "Bearer " + token);
req.setHeader("Accept", "application/json");
req.setHeader("X-Custom-Header", "my-value");
var resp = req.send();
Show test script
<script runat="server">
/*
 * Chapter: HttpRequestInstance Methods
 *
 * Proves against a public echo endpoint that echoes request headers back:
 *   1. clearHeaders(), removeHeader(), send() and setHeader() all exist.
 *   2. setHeader() actually puts the header on the outgoing request.
 *   3. Calling setHeader() twice for the same name REPLACES the value
 *      rather than appending a second header.
 *   4. removeHeader(name) removes only the named header.
 *   5. send() returns an HttpResponseInstance and performs the request.
 *   6. postData reaches the server (write-only property, assignment works).
 *
 * EXPECTED OUTPUT: every line starts with PASS.
 */

function assert(id, actual, expected) {
    var got;
    try { got = "" + actual; } catch (ex) { got = "THREW: " + ("" + ex.message); }
    Platform.Response.Write((got === "" + expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}

var req = new Script.Util.HttpRequest("https://postman-echo.com/post");

/* 1. All four documented methods exist on the instance. */
assert("clearHeaders() exists", typeof req.clearHeaders, "clrmethodinfo");
assert("removeHeader() exists", typeof req.removeHeader, "clrmethodinfo");
assert("send() exists", typeof req.send, "clrmethodinfo");
assert("setHeader() exists", typeof req.setHeader, "clrmethodinfo");

/* 2. Configure the request: one kept header, one replaced, one removed. */
req.method = "POST";
req.contentType = "application/json";
req.setHeader("X-Kept", "x-kept");
req.setHeader("X-Replaced", "first-value");
req.setHeader("X-Replaced", "second-value");
req.setHeader("X-Dropped", "drop-me");
req.removeHeader("X-Dropped");
req.postData = "{\"probe\":\"body-reached-server\"}";

var resp = req.send();
assert("send() returns a response object", typeof resp, "clr");
assert("send() performed the POST (statusCode 200)", resp.statusCode, "200");

var body = "" + resp.content;

/* 3. setHeader() put the kept header on the wire. */
assert("setHeader() sends the custom header", body.indexOf("x-kept") > -1, "true");

/* 4. A second setHeader() for the same name replaced the first value. */
assert("setHeader() twice keeps the second value", body.indexOf("second-value") > -1, "true");
assert("setHeader() twice discards the first value", body.indexOf("first-value") > -1, "false");

/* 5. removeHeader() removed only the named header. */
assert("removeHeader() removed the named header", body.indexOf("drop-me") > -1, "false");

/* 6. The write-only postData actually reached the server. */
assert("postData reached the server", body.indexOf("body-reached-server") > -1, "true");

/* 7. clearHeaders() removes every custom header — no header echoed back. */
var req2 = new Script.Util.HttpRequest("https://postman-echo.com/post");
req2.method = "POST";
req2.setHeader("X-Cleared", "cleared-value");
req2.clearHeaders();
req2.postData = "x";
var resp2 = req2.send();
var body2 = "" + resp2.content;
assert("clearHeaders() removed the custom header", body2.indexOf("cleared-value") > -1, "false");
</script>

HttpResponseInstance Properties

HttpResponseInstance has the same shape for HttpGet and HttpRequest, but only HttpRequest populates the response metadata — on HttpGet, contentType is empty and the headers enumeration yields nothing (see the bug callout on Script.Util.HttpGet).

The resp object returned by req.send() has these properties:

Property Type Description
content CLR string Response body (must use String() to convert)
contentType string The content type returned in the response
encoding string Documented as the response encoding, but always empty — read the charset from the content-type header instead
headers object Response headers as a CLR object — not directly indexable; read via the for..in pattern below
returnStatus number A status value: 0 = OK, 1 = Empty URL, 2 = Call failed, 3 = Call succeeded with empty content
statusCode number HTTP status code — a CLR value: convert with Number() before === or switch (relational operators like >= 400 work on it directly)

resp.content.length returns -1 no matter how long the body actually is — a 40-character body still reports -1. Unlike every other CLR value on this object, typeof resp.content.length already reports number, so the usual CLR tell is missing and the wrong answer is invisible on inspection. Emptiness guards written as resp.content.length > 0 are therefore always false, and if (resp.content.length) is always truthy. Measure the length as String(resp.content).length instead.

Show test script
<script runat="server">
/*
 * Chapter: HttpResponseInstance Properties
 *
 * Proves, for the object returned by send():
 *   1. content is a CLR string that must be converted before use.
 *   2. contentType IS populated on Script.Util.HttpRequest.
 *   3. headers is a CLR object whose for..in enumeration yields real entries.
 *   4. returnStatus is 0 (OK) for a successful call.
 *   5. statusCode carries the HTTP status.
 *
 *   DEVIATIONS from the official Salesforce docs, each marked "DEV":
 *     - encoding is documented as a populated response property but is
 *       ALWAYS an empty string — even when the response Content-Type
 *       carries a charset.
 *     - statusCode and returnStatus are CLR values, so strict equality
 *       against a JavaScript number literal is always false.
 *
 * EXPECTED OUTPUT: every line starts with PASS.
 */

function assert(id, actual, expected) {
    var got;
    try { got = "" + actual; } catch (ex) { got = "THREW: " + ("" + ex.message); }
    Platform.Response.Write((got === "" + expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}

var req = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");
req.method = "GET";
var resp = req.send();

/* 1. content is a CLR value, not a JS string. */
assert("content is a CLR value", typeof resp.content, "clr");
assert("content converts to a non-empty JS string", ("" + resp.content).length > 0, "true");

/* 2. contentType IS populated on HttpRequest. */
assert("contentType is populated", ("" + resp.contentType).length > 0, "true");
assert("contentType carries the charset", ("" + resp.contentType).indexOf("charset") > -1, "true");

/* 3. DEVIATION — encoding is empty although the content-type has a charset. */
assert("DEV encoding is always empty (docs: populated response property)", resp.encoding, "");

/* 4. headers is a CLR object with real enumerable entries. */
assert("headers is a CLR object", typeof resp.headers, "clr");
var headerCount = 0;
for (var k in resp.headers) { headerCount = headerCount + 1; }
assert("headers enumeration yields real entries", headerCount > 0, "true");

/* 5. returnStatus / statusCode values. */
assert("returnStatus is 0 (OK)", resp.returnStatus, "0");
assert("statusCode is 200", resp.statusCode, "200");

/* 6. DEVIATION — CLR values never satisfy strict equality with a number. */
assert("DEV statusCode === 200 is false (docs: number)", resp.statusCode === 200 ? "true" : "false", "false");
assert("DEV returnStatus === 0 is false (docs: number)", resp.returnStatus === 0 ? "true" : "false", "false");

/* 7. BUG — .length on the raw CLR content is always -1, and typeof already
 *    says "number" so the usual CLR tell is missing. */
assert("BUG raw content.length is -1 whatever the body is", resp.content.length, "-1");
assert("BUG typeof raw content.length is number, hiding the bug", typeof resp.content.length, "number");
assert("workaround String(content).length is the real length", ("" + resp.content).length > 0, "true");
</script>

Reading response headers

Direct access — resp.headers["Content-Type"], .Get(), .Item(), or String(resp.headers[key]) — throws “Use of Common Language Runtime (CLR) is not allowed”. However, a for..in loop over resp.headers yields keys shaped "[Name, Value]" — the value is embedded in the key string itself. Strip the [ ] wrapper and split on the first ", " to build a plain header map without ever reading a CLR value:

/**
 * Build a plain { name: value } header map from an HttpResponse.
 * Reads only the for..in enumeration keys (shaped "[Name, Value]") so it never
 * touches a CLR value — avoiding "Use of CLR is not allowed".
 * @param {object} resp - the response returned by req.send()
 * @returns {object} map of lowercased header name => value string
 */
function getHeaderMap(resp) {
    var map = {};
    for (var k in resp.headers) {
        var pair = String(k);
        // Enumeration keys are wrapped in [ ] — strip them.
        if (pair.charAt(0) === "[") { pair = pair.substring(1); }
        if (pair.charAt(pair.length - 1) === "]") { pair = pair.substring(0, pair.length - 1); }
        var idx = pair.indexOf(", ");
        if (idx > -1) {
            map[pair.substring(0, idx).toLowerCase()] = pair.substring(idx + 2);
        }
    }
    return map;
}

var resp = req.send();
var headers = getHeaderMap(resp);
var contentType = headers["content-type"]; // "application/json; charset=utf-8"

Header names are lowercased in the map above so lookups are case-insensitive. A missing header returns undefined.

Show test script
<script runat="server">
/*
 * Chapter: Reading response headers
 *
 * Proves:
 *   1. Direct indexing — resp.headers["Content-Type"] — THROWS, although the
 *      official docs example uses exactly that access (marked DEV).
 *   2. The documented for..in workaround yields keys shaped "[Name, Value]".
 *   3. getHeaderMap() from the page builds a usable lower-cased header map.
 *   4. A missing header returns undefined from that map.
 *
 * EXPECTED OUTPUT: every line starts with PASS.
 */

function assert(id, actual, expected) {
    var got;
    try { got = "" + actual; } catch (ex) { got = "THREW: " + ("" + ex.message); }
    Platform.Response.Write((got === "" + expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrowsFragment(id, fn, fragment) {
    var msg = "";
    try { fn(); msg = "did NOT throw"; } catch (ex) { msg = "" + ex.message; }
    Platform.Response.Write((msg.indexOf(fragment) > -1 ? "PASS " : "FAIL ") + id + " -> " + msg + "\n");
}

/**
 * Build a plain { name: value } header map from an HttpResponse.
 * Reads only the for..in enumeration keys (shaped "[Name, Value]") so it never
 * touches a CLR value — avoiding "Use of CLR is not allowed".
 * @param {object} resp - the response returned by req.send()
 * @returns {object} map of lowercased header name => value string
 */
function getHeaderMap(resp) {
    var map = {};
    for (var k in resp.headers) {
        var pair = String(k);
        // Enumeration keys are wrapped in [ ] — strip them.
        if (pair.charAt(0) === "[") { pair = pair.substring(1); }
        if (pair.charAt(pair.length - 1) === "]") { pair = pair.substring(0, pair.length - 1); }
        var idx = pair.indexOf(", ");
        if (idx > -1) {
            map[pair.substring(0, idx).toLowerCase()] = pair.substring(idx + 2);
        }
    }
    return map;
}

var req = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");
req.method = "GET";
var resp = req.send();

/* 1. DEVIATION — the documented direct access throws. */
assertThrowsFragment("DEV resp.headers[\"Content-Type\"] throws (docs show this access)", function () {
    return resp.headers["Content-Type"];
}, "Use of Common Language Runtime (CLR) is not allowed");

/* 2. The for..in enumeration keys carry the value inside the key string. */
var firstKey = "";
for (var k in resp.headers) { if (firstKey === "") { firstKey = String(k); } }
assert("enumeration keys are wrapped in [ ]", firstKey.charAt(0), "[");
assert("enumeration keys embed the value after a comma", firstKey.indexOf(", ") > -1, "true");

/* 3. workaround — getHeaderMap() returns the content-type value. */
var headers = getHeaderMap(resp);
assert("workaround getHeaderMap() returns the content-type", headers["content-type"].indexOf("text/plain") > -1, "true");
assert("workaround header names are lower-cased", headers["content-type"].length > 0, "true");

/* 4. A missing header is undefined, not an error. */
assert("a missing header returns undefined", typeof headers["x-does-not-exist"], "undefined");
</script>

Checking the status code

resp.statusCode is a CLR value, so convert it once and compare the JavaScript number:

var resp = req.send();
var status = Number(resp.statusCode);
if (status === 200) {
    var data = Platform.Function.ParseJSON(String(resp.content));
} else if (status === 404) {
    Write("Not found.");
} else {
    Write("Error: " + status);
}

Avoid ==. It appears to work on a populated status code, but loose equality against any CLR value backed by a .NET null throws Value cannot be null. Parameter name: value=== returns false safely instead. Relational comparisons (status >= 400) are correct with or without the conversion.

switch needs the conversion too: switch (resp.statusCode) { case 200: … } never matches a case and silently runs the default branch, without throwing. switch (Number(resp.statusCode)) behaves as expected.

Show test script
<script runat="server">
/*
 * Chapter: Checking the status code
 *
 * Proves:
 *   1. resp.statusCode is a CLR value, NOT a JavaScript number.
 *   2. Strict equality against a number literal is ALWAYS false — this is
 *      the deviation the chapter warns about (marked DEV).
 *   3. Loose equality (==) does discriminate correctly on a POPULATED
 *      status code — recorded for completeness, not as a recommendation:
 *      == throws on a CLR value backed by a .NET null, so the chapter
 *      steers readers to Number() + === instead.
 *   4. The Number() conversion the chapter recommends makes === work.
 *
 * EXPECTED OUTPUT: every line starts with PASS.
 */

function assert(id, actual, expected) {
    var got;
    try { got = "" + actual; } catch (ex) { got = "THREW: " + ("" + ex.message); }
    Platform.Response.Write((got === "" + expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}

var req = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");
req.method = "GET";
var resp = req.send();

/* 1. statusCode is a CLR value. */
assert("typeof statusCode is clr, not number", typeof resp.statusCode, "clr");
assert("statusCode stringifies to 200", "" + resp.statusCode, "200");

/* 2. DEVIATION — strict equality never matches a JS number literal. */
assert("DEV statusCode === 200 is false (docs type it number)", resp.statusCode === 200 ? "true" : "false", "false");
assert("DEV statusCode === 404 is also false", resp.statusCode === 404 ? "true" : "false", "false");

/* 3. Loose equality discriminates on a populated status code — but it
 *    throws on a .NET-null-backed CLR value, so it is not the idiom. */
assert("statusCode == 200 is true", resp.statusCode == 200 ? "true" : "false", "true");
assert("statusCode == 404 is false", resp.statusCode == 404 ? "true" : "false", "false");

/* 4. recommended — convert once with Number(), then === works. */
var status = Number(resp.statusCode);
assert("workaround typeof Number(statusCode) is number", typeof status, "number");
assert("workaround Number(statusCode) === 200 is true", status === 200 ? "true" : "false", "true");

/* 5. DEVIATION — switch on the raw CLR value silently hits default. */
var branch = "";
switch (resp.statusCode) {
    case 200: branch = "matched200"; break;
    default: branch = "default";
}
assert("DEV switch on the raw statusCode falls through to default", branch, "default");
var branch2 = "";
switch (Number(resp.statusCode)) {
    case 200: branch2 = "matched200"; break;
    default: branch2 = "default";
}
assert("workaround switch on Number(statusCode) matches case 200", branch2, "matched200");

/* 6. Relational operators are CORRECT on the raw value — no conversion. */
assert("raw statusCode >= 400 is false on a 200", resp.statusCode >= 400 ? "true" : "false", "false");
assert("raw statusCode < 300 is true on a 200", resp.statusCode < 300 ? "true" : "false", "true");
assert("raw statusCode > 199 is true on a 200", resp.statusCode > 199 ? "true" : "false", "true");
</script>

Examples

GET request with auth

var token = Platform.Function.Lookup("Config", "accessToken", "key", "sfmcRest");
var req = new Script.Util.HttpRequest("https://mc.rest.example.com/v2/contacts");
req.method = "GET";
req.setHeader("Authorization", "Bearer " + token);
req.setHeader("Accept", "application/json");

try {
    var resp = req.send();
    var status = Number(resp.statusCode);
    if (status === 200) {
        var data = Platform.Function.ParseJSON(String(resp.content));
        Platform.Response.ContentType = "application/json";
        Write(Stringify(data));
    } else {
        Write(Stringify({ status: status, statusMessage: "Upstream Error", error: status }));
    }
} catch(e) {
    Write(Stringify({ status: 500, statusMessage: "Internal Server Error", error: e.message }));
}

POST JSON body

var payload = Stringify({
    DefinitionKey: "SomeJourneyKey",
    ContactKey: subscriberKey,
    EventDefinitionKey: "APIEvent-...",
    Data: { FirstName: firstName, Plan: planType }
});

var req = new Script.Util.HttpRequest("https://mc.rest.example.com/interaction/v1/events");
req.method = "POST";
req.contentType = "application/json";
req.setHeader("Authorization", "Bearer " + token);
req.postData = payload;

var resp = req.send();
var result = Platform.Function.ParseJSON(String(resp.content));

PUT request (update)

var req = new Script.Util.HttpRequest("https://api.example.com/items/42");
req.method = "PUT";
req.contentType = "application/json";
req.setHeader("Authorization", "Bearer " + token);
req.postData = Stringify({ name: "Updated Name", active: true });
var resp = req.send();

DELETE request

var req = new Script.Util.HttpRequest("https://api.example.com/items/42");
req.method = "DELETE";
req.setHeader("Authorization", "Bearer " + token);
var resp = req.send();

With timeout

var req = new Script.Util.HttpRequest("https://slow.api.example.com/data");
req.method = "GET";
req.timeout = 10; // 10 second timeout
req.setHeader("Authorization", "Bearer " + token);
var resp = req.send();
Show test script
<script runat="server">
/*
 * Chapter: Examples
 *
 * Proves the shape every example on the page relies on, using one request:
 *   1. A GET with setHeader() + try/catch returns a parseable body.
 *   2. String(resp.content) + ParseJSON() is the documented read path.
 *   3. A POST with contentType + postData delivers a JSON body.
 *   4. The timeout example's value (10) is accepted as seconds.
 *
 * EXPECTED OUTPUT: every line starts with PASS.
 */

function assert(id, actual, expected) {
    var got;
    try { got = "" + actual; } catch (ex) { got = "THREW: " + ("" + ex.message); }
    Platform.Response.Write((got === "" + expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}

/* 1-3. POST JSON body with headers, then read via String() + ParseJSON. */
var caught = "none";
var parsed = null;
var code = "";
try {
    var req = new Script.Util.HttpRequest("https://postman-echo.com/post");
    req.method = "POST";
    req.contentType = "application/json";
    req.setHeader("Accept", "application/json");
    req.timeout = 10;
    req.postData = "{\"plan\":\"example-plan\"}";
    var resp = req.send();
    code = "" + resp.statusCode;
    parsed = Platform.Function.ParseJSON("" + resp.content);
} catch (e) {
    caught = "" + e.message;
}
assert("the documented try/catch example did not throw", caught, "none");
assert("statusCode == 200", code, "200");
assert("String(content) + ParseJSON returns an object", typeof parsed, "object");
assert("the JSON body reached the server", ("" + Platform.Function.Stringify(parsed)).indexOf("example-plan") > -1, "true");

/* 4. The timeout example's value is accepted (seconds, not milliseconds). */
var toReq = new Script.Util.HttpRequest("https://ssjs.guide/robots.txt");
toReq.timeout = 10;
assert("timeout = 10 (seconds) is accepted", toReq.timeout, "10");
</script>

Complete REST API Helper Pattern

function callRestApi(method, url, token, body) {
    var req = new Script.Util.HttpRequest(url);
    req.method = method;
    req.setHeader("Authorization", "Bearer " + token);
    req.setHeader("Accept", "application/json");

    if (body) {
        req.contentType = "application/json";
        req.postData = Stringify(body);
    }

    var resp = req.send();
    var parsed = Platform.Function.ParseJSON(String(resp.content) + "");
    // Convert the CLR statusCode to a real number so callers can use ===.
    return { status: Number(resp.statusCode), data: parsed };
}

var result = callRestApi("GET", "https://api.example.com/v1/users", accessToken, null);
if (result.status === 200) {
    Write("Users: " + result.data.count);
}
Show test script
<script runat="server">
/*
 * Chapter: Complete REST API Helper Pattern
 *
 * Proves the helper function shipped on the page actually works:
 *   1. callRestApi() performs the request and returns { status, data }.
 *   2. status is a REAL JavaScript number because of the Number() conversion,
 *      so callers can use === (the whole point of that conversion).
 *   3. The body argument is sent when supplied and omitted when null.
 *
 * EXPECTED OUTPUT: every line starts with PASS.
 */

function assert(id, actual, expected) {
    var got;
    try { got = "" + actual; } catch (ex) { got = "THREW: " + ("" + ex.message); }
    Platform.Response.Write((got === "" + expected ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}

function callRestApi(method, url, token, body) {
    var req = new Script.Util.HttpRequest(url);
    req.method = method;
    req.setHeader("Authorization", "Bearer " + token);
    req.setHeader("Accept", "application/json");

    if (body) {
        req.contentType = "application/json";
        req.postData = Stringify(body);
    }

    var resp = req.send();
    var parsed = Platform.Function.ParseJSON("" + resp.content + "");
    // Convert the CLR statusCode to a real number so callers can use ===.
    return { status: Number(resp.statusCode), data: parsed };
}

Platform.Load("core", "1.1.5");

/* 1-3. POST with a body — the echo endpoint reflects what was sent. */
var result = callRestApi("POST", "https://postman-echo.com/post", "dummy-token", { plan: "helper-plan" });
assert("callRestApi returned an object", typeof result, "object");
assert("status is a real JavaScript number", typeof result.status, "number");
assert("status === 200 works after Number() conversion", result.status === 200 ? "true" : "false", "true");
assert("data is a parsed object", typeof result.data, "object");
assert("the body was sent", ("" + Platform.Function.Stringify(result.data)).indexOf("helper-plan") > -1, "true");
</script>

See Also