The previous lessons built a REST API. This one calls one, from ColdFusion, using cfhttp to send the request and deserializeJSON to turn the response back into a struct or array your code can actually work with.
Learning Objectives
After completing this lesson, you'll be able to:
- Perform a GET request and parse the JSON response.
- Send a POST request with a JSON body and the right headers.
- Check the response status code before trusting the result, and know why that check might silently never run.
How a Request Actually Gets Made and Parsed
cfhttp Sends the Request
External API Processes It
JSON Response Returned
deserializeJSON() Converts It to a Struct/Array
A Basic GET Request
cfhttp(method = "GET", url = "https://api.example.com/users", result = "apiResponse") {
cfhttpparam(type = "url", name = "page", value = "1");
cfhttpparam(type = "url", name = "limit", value = "20");
}
data = deserializeJSON(apiResponse.fileContent);<cfhttp method="GET" url="https://api.example.com/users" result="apiResponse">
<cfhttpparam type="url" name="page" value="1">
<cfhttpparam type="url" name="limit" value="20">
</cfhttp>
<cfset data = deserializeJSON(apiResponse.fileContent)>cfhttpparam type="url" appends a query-string pair to the request URL, this example produces GET /users?page=1&limit=20.
What's in the Result Struct
| Field | Meaning |
|---|---|
| statusCode | The HTTP status code and reason string, e.g. "200 OK" |
| fileContent | The response body (usually a string) |
| responseHeader | A structure of the response's headers |
| mimeType | The Content-Type of the response |
| errorDetail | An error message, if something went wrong |
cfhttpparam's Types
| type | Sends |
|---|---|
| header | A custom HTTP header (not URL-encoded) |
| url | A query-string name/value pair appended to the request URL |
| body | The raw request body, as-is |
| formfield | A form field, URL-encoded, simulating a standard HTML form POST |
| xml | A request body with Content-Type: text/xml |
| cookie | A cookie sent as a header, URL-encoded |
| file | The contents of a specified file |
A Real Example: POST With a JSON Body and Auth Header
payload = { name: "John Doe", email: "john@example.com" };
cfhttp(method = "POST", url = "https://api.example.com/users", result = "apiResponse") {
cfhttpparam(type = "header", name = "Authorization", value = "Bearer #accessToken#");
cfhttpparam(type = "header", name = "Content-Type", value = "application/json");
cfhttpparam(type = "body", value = serializeJSON(payload));
}Setting Content-Type: application/json explicitly matters, without it, some APIs won't correctly interpret a JSON body sent as type="body".
A Real Gotcha: throwonerror Defaults to true
cfhttp's throwonerror attribute defaults to true, meaning an error response code throws an exception rather than just returning it in statusCode for you to inspect. Code that checks apiResponse.statusCode after the call, expecting to branch on a 404 or 500 there, never reaches that check at all unless throwonerror is explicitly set to false, the exception interrupts execution first.
cfhttp(method = "GET", url = "https://api.example.com/users/999", result = "apiResponse", throwonerror = false) {
}
if (listFirst(apiResponse.statusCode, " ") == "200") {
data = deserializeJSON(apiResponse.fileContent);
} else {
writeLog(type = "error", text = "API error: #apiResponse.statusCode# - #apiResponse.fileContent#");
}listFirst(statusCode, " ") pulls just the numeric code ("200") out of the full status string ("200 OK") for a clean comparison.
Real Extras on Lucee's http Tag
| Attribute | What It Adds |
|---|---|
| cachedWithin | Caches a response for a given timespan, or for the current request, avoiding a repeat call for identical parameters |
| connectionTimeout / socketTimeout | Separate timeouts for the TCP handshake versus the actual data transfer (Lucee 6.2.2.66+) |
| autoCert | Automatically installs SSL certificates for HTTPS connections (Lucee 6.1.0.132+) |
| redirect="Lax" | Returns the file content for a POST/DELETE redirect instead of dropping it (Lucee 7.0.0.208+) |
None of these have a direct equivalent on Adobe ColdFusion's cfhttp.
Common Beginner Mistakes
Checking statusCode without setting throwonerror="false"
By default, an error response throws an exception instead of letting the code reach the statusCode check at all. Either set throwonerror="false" and check statusCode, or wrap the call in cftry/cfcatch instead.
Sending a JSON body without setting Content-Type
Some APIs won't correctly parse the body as JSON without that header explicitly set to application/json.
Assuming deserializeJSON always returns a struct
It returns whatever the JSON actually represents, a struct for a JSON object, an array for a JSON array. Code that assumes struct-only access can fail on a top-level array response.
Putting API-calling logic directly inside a .cfm page
This makes authentication, error handling, timeouts, and reuse harder to manage consistently. A dedicated CFC/service layer for API calls keeps that logic in one place.
Best Practices
- Set throwonerror="false" explicitly when you intend to branch on statusCode yourself, rather than relying on exception handling.
- Always set Content-Type when sending a JSON body.
- Put API-calling logic in a dedicated CFC rather than inline in a .cfm page, for reusable authentication, error handling, and logging.
- On Lucee, use cachedWithin for GET requests that don't need a fresh call every single time.
Interview Questions
What does cfhttp's throwonerror default to, and why does that matter?
It defaults to true, meaning an error response throws an exception rather than letting you inspect statusCode afterward. Code that checks statusCode without setting throwonerror="false" never reaches that check on a real error.
What's the difference between cfhttpparam type="url" and type="formfield"?
type="url" appends a query-string parameter to the request URL. type="formfield" sends a URL-encoded form field in the request body, simulating a standard HTML form submission.
Why explicitly set the Content-Type header when POSTing JSON?
Without it, some APIs won't correctly interpret a raw type="body" payload as JSON, even if the content itself is valid JSON.
What's a real Lucee-specific feature that has no Adobe cfhttp equivalent?
cachedWithin, which caches a response for a given timespan (or the current request) to avoid repeating an identical call.
Summary
In this lesson, you performed a GET request with query parameters, sent a POST request with a JSON body and an Authorization header, parsed responses with deserializeJSON, and covered a real gotcha with throwonerror's default that silently prevents status-code checking from ever running.
What's Next?
The next lesson covers JSON responses in more depth: building and returning well-structured JSON from your own ColdFusion endpoints.