Skip to main content

ControlForge HTTP Client Guide

James M. Belcher Founder, JMB Technical Services LLC April 2026 | ControlForge v1.0.535


1. Overview

ControlForge provides 16 built-in functions for making HTTP requests from Structured Text. Call REST APIs, fetch data from web services, post telemetry to cloud platforms, and integrate with any HTTP-based system — all from your PLC program.

(* Simple GET — one line *)
body := HTTP_GET_BODY('http://api.example.com/status');

(* Full control *)
resp := HTTP_GET('http://api.example.com/data');
IF HTTP_OK(resp) THEN
data := HTTP_BODY(resp);
END_IF;

Response Pattern

Most HTTP functions return a response map with these fields:

FieldTypeDescription
statusINTHTTP status code (200, 404, 500, etc.)
bodySTRINGResponse body
headersMAPResponse headers (when available)
errorSTRINGError message (empty on success)

Use the helper functions HTTP_STATUS, HTTP_BODY, HTTP_OK, and HTTP_ERROR to extract fields from the response.


2. Simple Request Functions

HTTP_GET — GET Request

resp := HTTP_GET('http://10.0.0.144:8086/health');

IF HTTP_OK(resp) THEN
body := HTTP_BODY(resp);
END_IF;
ParamTypeRequiredDescription
urlSTRINGYesRequest URL
timeout_secINTNoTimeout in seconds (default: 5)

Returns: response map.

HTTP_GET_BODY — GET, Returns Body Directly

The simplest form — returns the response body as a string, or empty string on error.

body := HTTP_GET_BODY('http://10.0.0.50/api/info');
IF LEN(body) > 0 THEN
(* Parse body... *)
END_IF;

No response map, no status code — just the body. Use this for quick reads where you don't need error details.

HTTP_POST — POST Request

resp := HTTP_POST('http://10.0.0.144:8086/api/v2/write',
'temperature,site=Plant1 value=72.5',
'text/plain');
ParamTypeRequiredDescription
urlSTRINGYesRequest URL
bodySTRINGYesRequest body
content_typeSTRINGNoContent-Type header (default: application/json)
timeout_secINTNoTimeout in seconds (default: 5)

HTTP_POST_JSON — POST JSON, Parse Response

Posts JSON and attempts to parse the response as JSON automatically.

resp := HTTP_POST_JSON('http://api.example.com/devices',
'{"name": "Pump-1", "type": "VFD"}');
(* If response is JSON, resp is a parsed JSON object *)
(* If not, resp is a standard {status, body, error} map *)

HTTP_PUT — PUT Request

resp := HTTP_PUT('http://10.0.0.50/api/config',
'{"scan_time_ms": 100}');

Same parameters as HTTP_POST.

HTTP_PATCH — PATCH Request

resp := HTTP_PATCH('http://10.0.0.50/api/config',
'{"log_level": "debug"}');

Same parameters as HTTP_POST.

HTTP_DELETE — DELETE Request

resp := HTTP_DELETE('http://10.0.0.50/api/programs/old_program');
ParamTypeRequiredDescription
urlSTRINGYesRequest URL
timeout_secINTNoTimeout in seconds (default: 5)

HTTP_HEAD — HEAD Request

Returns status and headers only (no body). Useful for checking if a resource exists.

resp := HTTP_HEAD('http://10.0.0.50/api/info');
code := HTTP_STATUS(resp);
IF code = 200 THEN
(* Resource exists *)
END_IF;

3. Advanced: HTTP_REQUEST

Full control over method, headers, and body.

(* Build custom headers *)
hdrs := HTTP_SET_HEADER('', 'Authorization', 'Bearer my-token');
hdrs := HTTP_SET_HEADER(hdrs, 'X-Custom-Header', 'ControlForge');

(* Make request *)
resp := HTTP_REQUEST('POST',
'http://api.example.com/data',
'{"temp": 72.5}',
hdrs,
10); (* 10 second timeout *)
ParamTypeRequiredDescription
methodSTRINGYesHTTP method (GET, POST, PUT, DELETE, PATCH, etc.)
urlSTRINGYesRequest URL
bodySTRINGNoRequest body
headersMAP/STRINGNoHeaders map or JSON handle
timeout_secINTNoTimeout in seconds (default: 5)

Returns: response map with status, body, headers, and error.


4. Response Helper Functions

HTTP_STATUS — Get Status Code

code := HTTP_STATUS(resp); (* 200, 404, 500, etc. *)

HTTP_BODY — Get Response Body

body := HTTP_BODY(resp);

HTTP_OK — Check for Success (2xx)

IF HTTP_OK(resp) THEN
(* Status is 200-299 *)
END_IF;

HTTP_ERROR — Get Error Message

err := HTTP_ERROR(resp);
IF LEN(err) > 0 THEN
DEBUG_ERROR('http', CONCAT('Request failed: ', err));
END_IF;

HTTP_HEADERS — Get All Response Headers

hdrs := HTTP_HEADERS(resp);

HTTP_GET_HEADER — Get Single Header

Case-insensitive header name lookup.

content_type := HTTP_GET_HEADER(resp, 'Content-Type');
server := HTTP_GET_HEADER(resp, 'Server');

HTTP_SET_HEADER — Build Request Headers

Creates or adds to a headers map for use with HTTP_REQUEST.

hdrs := HTTP_SET_HEADER('', 'Authorization', 'Bearer token123');
hdrs := HTTP_SET_HEADER(hdrs, 'Content-Type', 'application/json');
hdrs := HTTP_SET_HEADER(hdrs, 'Accept', 'application/json');

Pass empty string as the first argument to create a new headers map.


5. Complete Example: REST API Integration

Poll a weather API and publish results to MQTT:

PROGRAM POU_WeatherPoll
VAR
state : INT := 0;
scan_count : DINT := 0;
resp : STRING;
body : STRING;
doc : STRING;
temp_f : REAL;
humidity : INT;
ok : BOOL;
END_VAR

scan_count := scan_count + 1;

CASE state OF
0: (* Initialize MQTT *)
ok := MQTT_CLIENT_CREATE('weather', 'tcp://10.0.0.144:1883', 'goplc-weather');
ok := MQTT_CLIENT_CONNECT('weather');
state := 10;

10: (* Poll weather every 600 scans (60s at 100ms) *)
IF (scan_count MOD 600) = 0 THEN
resp := HTTP_GET('http://api.weather.local/current');

IF HTTP_OK(resp) THEN
body := HTTP_BODY(resp);
doc := JSON_PARSE(body);
temp_f := JSON_GET_REAL(doc, 'temp_f');
humidity := JSON_GET_INT(doc, 'humidity');

(* Publish to MQTT *)
MQTT_PUBLISH('weather', 'plant/weather/temp', REAL_TO_STRING(temp_f));
MQTT_PUBLISH('weather', 'plant/weather/humidity', INT_TO_STRING(humidity));
ELSE
DEBUG_WARN('weather', CONCAT('API error: ', HTTP_ERROR(resp)));
END_IF;
END_IF;
END_CASE;
END_PROGRAM

6. Complete Example: Webhook Notifications

Send alarm notifications to a webhook endpoint:

PROGRAM POU_AlarmWebhook
VAR
temperature : REAL;
alarm_active : BOOL := FALSE;
alarm_sent : BOOL := FALSE;
resp : STRING;
payload : STRING;
hdrs : STRING;
END_VAR

(* Check alarm condition *)
alarm_active := temperature > 180.0;

(* Send webhook on alarm rising edge *)
IF alarm_active AND NOT alarm_sent THEN
payload := JSON_OBJECT(
'event', 'HIGH_TEMP_ALARM',
'value', temperature,
'threshold', 180.0,
'source', 'ControlForge-Plant1'
);

hdrs := HTTP_SET_HEADER('', 'Authorization', 'Bearer webhook-secret');
resp := HTTP_REQUEST('POST',
'https://hooks.example.com/alerts',
JSON_STRINGIFY(payload),
hdrs);

IF HTTP_OK(resp) THEN
alarm_sent := TRUE;
END_IF;
END_IF;

(* Reset when alarm clears *)
IF NOT alarm_active THEN
alarm_sent := FALSE;
END_IF;

END_PROGRAM

7. Complete Example: Cross-PLC Communication

Read variables from another ControlForge instance via its REST API:

PROGRAM POU_RemotePLC
VAR
scan_count : DINT := 0;
body : STRING;
doc : STRING;
remote_temp : REAL;
remote_running : BOOL;
END_VAR

scan_count := scan_count + 1;

(* Poll remote PLC every 50 scans (5s at 100ms) *)
IF (scan_count MOD 50) = 0 THEN
body := HTTP_GET_BODY('http://10.0.0.51:8082/api/variables');

IF LEN(body) > 0 THEN
doc := JSON_PARSE(body);

IF JSON_HAS(doc, 'temperature') THEN
remote_temp := JSON_GET_REAL(doc, 'temperature.value');
END_IF;

IF JSON_HAS(doc, 'motor_running') THEN
remote_running := JSON_GET_BOOL(doc, 'motor_running.value');
END_IF;
END_IF;
END_IF;

END_PROGRAM

Appendix A: Quick Reference

FunctionParametersReturnsDescription
HTTP_GET(url [, timeout])Response mapGET request
HTTP_GET_BODY(url [, timeout])STRINGGET, returns body directly
HTTP_POST(url, body [, content_type] [, timeout])Response mapPOST request
HTTP_POST_JSON(url, json_body [, timeout])Parsed JSON or mapPOST JSON, auto-parse response
HTTP_PUT(url, body [, content_type] [, timeout])Response mapPUT request
HTTP_PATCH(url, body [, content_type] [, timeout])Response mapPATCH request
HTTP_DELETE(url [, timeout])Response mapDELETE request
HTTP_HEAD(url [, timeout])Response mapHEAD (status + headers only)
HTTP_REQUEST(method, url [, body] [, headers] [, timeout])Response mapFull control request
HTTP_STATUS(response)INTExtract status code
HTTP_BODY(response)STRINGExtract body
HTTP_OK(response)BOOLTRUE if status 200-299
HTTP_ERROR(response)STRINGExtract error message
HTTP_HEADERS(response)MAPExtract all headers
HTTP_GET_HEADER(response, name)STRINGGet single header (case-insensitive)
HTTP_SET_HEADER(headers, name, value)MAPBuild/add to headers map

ControlForge v1.0.535 | 16 HTTP Client Functions | REST Integration from ST

© 2026 JMB Technical Services LLC. All rights reserved. Back to All Guides