REST agent
Turns plain HTTP POSTs into SCADA measurements — the driver address, the two ingest endpoints, and the docker image's own config.
This is the protocol reference for the REST agent. For a guided walkthrough — create the agent, upload a config, watch it live — see Connect a REST agent. For everything else in the config format — alarms, commands, interlocks, value conversion — see Agent configuration.
driver.path is a string you choose — there's no fixed format. It's both the measurement's join key in the config and the URL segment the agent listens on, so pick whatever's convenient: sequential numbers, register addresses, your own naming scheme.
{
"name": "brk_10",
"equipment": "eqp",
"type": "discrete",
"store": true,
"driver": { "type": "discrete", "path": "1" }
}POST /addr/{path} — submit one measurement, where {path} matches a configured driver.path:
value— required, a number. Whether it lands as discrete or analogical follows the measurement's configuredtype.timestamp— optional, RFC 3339. Defaults to the current time.quality— optional, boolean.true(the default) is Good,falseis Bad.
curl -X POST http://localhost:9090/addr/1 \
-H "Content-Type: application/json" \
-d '{
"value": 2,
"timestamp": "2025-02-16T21:52:13Z",
"quality": true
}'Returns 404 if {path} isn't configured.
POST /batch — the same, for a JSON array. Each entry carries its own addr instead of putting it in the URL:
curl -X POST http://localhost:9090/batch \
-H "Content-Type: application/json" \
-d '[
{ "addr": "1", "value": 10 },
{ "addr": "2", "value": 11.5, "quality": false }
]'GET /health — liveness check, returns Healthy!.
The REST agent doesn't know how to reach your equipment on its own — set rest.webhook in its own config (below), and every command sent to this agent is forwarded there instead as its own request: POST, Content-Type: application/json, body { command, value }.
command— the matching entry from this agent's own config, unchanged:name,equipment,driver, andinterlockif one's set.value—nullfor a discrete command, since its value is already fixed incommand.driver.value. For an analogical command it's the setpoint supplied at runtime, since the config carries no value for those (see Agent configuration).
A discrete command, configured with a fixed value:
{
"name": "open",
"equipment": "brk_10",
"driver": { "type": "discrete", "value": 1, "path": "1" }
}{
"command": {
"name": "open",
"equipment": "brk_10",
"driver": { "type": "discrete", "value": 1, "path": "1" }
},
"value": null
}An analogical command — no value in the config, the setpoint travels in the webhook body instead:
{
"name": "setpoint",
"equipment": "brk_10",
"driver": { "type": "analogical", "path": "2" }
}{
"command": {
"name": "setpoint",
"equipment": "brk_10",
"driver": { "type": "analogical", "path": "2" }
},
"value": 42.5
}Any non-2xx response is treated as a failed command. Either way, the outcome is recorded against the request and queryable over GraphQL (requestUpdate), same as a command sent to any other protocol agent.
Image: khorde/rest-agent:latest. 9090 is the one port above — /addr, /batch, and /health all live on it — same compose shape as every agent (see Agent configuration). Save the service account key from Connect a REST agent next to it, renamed to service-user.json. Its own config.yaml:
rest:
host: 0.0.0.0
port: 9090
# Uncomment to forward commands received from SCADA to your own equipment:
# webhook: "http://host.docker.internal:8081"
agent:
broker:
ip: host.docker.internal
port: "4222"
tls: false
credentials:
url: http://auth.localhost:9310
service_account_path: service-user.json
command_timeout: 10sAgent configuration
The full schema shared by every protocol. Agent configuration.
Walk through it live
Create an agent, upload a config, and watch it over GraphQL. Connect a REST agent.
Other protocols
IEC 60870-5-104 (client), (server), and IEC 61850 agents work the same way.