Skip to content

REST API by Task: Worked Examples ​

Last updated

Common integration jobs, with the request and what comes back. All examples assume $TOKEN holds an access token from /api/auth/login (see REST API Reference) and the appliance is https://scada.local.

Read live values ​

MethodPathMinimum rolePurpose
GET/api/devicesviewerDevices with their settings
GET/api/devices/status/allviewerConnection status of every device, keyed by device id
GET/api/devices/status/summaryviewer{ connected, faulted } counts
GET/api/tags/live/:deviceIdviewerCurrent value of every subscribed tag on one device
GET/api/tags/subscriptionsviewerEvery subscribed tag with its settings
bash
curl -s https://scada.local/api/tags/live/3 -H "Authorization: Bearer $TOKEN"
json
{ "success": true, "data": [
  { "deviceId": 3, "tagPath": "HR100", "value": "182.4", "dataType": "float", "quality": "good", "timestamp": "2026-09-15T06:02:10.998Z" },
  { "deviceId": 3, "tagPath": "HR120", "value": "48213", "dataType": "int", "quality": "good", "timestamp": "2026-09-15T06:02:10.998Z" }
] }

/api/tags/live/:deviceId returns what the appliance has cached — it does not poll the device on demand. For tags on the built-in Local Tags device, look up that device's id in /api/devices (driver local). More tag endpoints: Tag API Reference.

Write a value ​

MethodPathMinimum roleBody
POST/api/tags/writeoperator{ deviceId, tagPath, value } — a device tag, or a local tag on the Local Tags device
POST/api/local-tags/writeoperator{ tagPath, value } — a local (memory) tag
bash
# Send a new setpoint to the press
curl -s -X POST https://scada.local/api/tags/write \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"deviceId":3,"tagPath":"HR200","value":185}'
# {"success":true,"data":{"deviceId":3,"tagPath":"HR200","value":185}}

A device write is refused with 403 unless the device has write access enabled, and with 403 You do not have permission to write to this tag. when a security level restricts the tag. Every accepted write is recorded in the audit log under the calling user. A local-tag write needs the tag to exist (404 Local tag not found), checks the tag's security level, and does not need write access enabled on the Local Tags device; the value is stored as text and converted to the tag's data type. See Writing Tag Values and Local Tags.

Alarms ​

MethodPathMinimum rolePurpose
GET/api/alarms/activeviewerAlarms that are active or unacknowledged
GET/api/alarms/historyviewerPast alarms; page, limit (≤ 200), severity, from, to (ISO), search
PUT/api/alarms/:id/acknowledgeoperatorAcknowledge one alarm; optional body { "comment": "…" }
POST/api/alarms/acknowledgeoperatorAcknowledge several: { "ids": [12, 13], "comment": "…" } (at most 500)
bash
curl -s "https://scada.local/api/alarms/history?severity=critical&from=2026-09-14T00:00:00Z&limit=100" \
  -H "Authorization: Bearer $TOKEN"
# { "success": true, "data": [ … ], "meta": { "total": 12, "page": 1, "limit": 100 } }

Acknowledging answers { "success": true, "data": { "acknowledged": true } }, or false for an id that doesn't exist or was already acknowledged (its first acknowledgement and comment stand). When the appliance requires acknowledgement comments, a request without one is refused with A comment is required to acknowledge an alarm. Rules, shelving and pipelines are in Alarms; notification settings, on-call schedules and the SMS queue in Notification settings.

History ​

MethodPathMinimum rolePurpose
GET/api/historian/tagsviewerTags that have history enabled
GET/api/historian/queryviewerPoints for one tag: deviceId, tagPath, start (required), stop, window + aggregate
GET/api/historian/exportviewerThe same as CSV
bash
curl -s -G https://scada.local/api/historian/query -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "deviceId=3" --data-urlencode "tagPath=HR100" \
  --data-urlencode "start=-8h" --data-urlencode "window=15m" --data-urlencode "aggregate=mean"

Aggregates, time handling and the unauthenticated /api/data/history.json are covered in Historian API.

Data tables (MES records, logs, lookups) ​

MethodPathMinimum rolePurpose
GET/api/custom-tables/by-name/:name/dataviewerRows; limit (≤ 1000), offset, search, sortBy, sortDir
POST/api/custom-tables/by-name/:name/dataeditorInsert a row
PUT/api/custom-tables/by-name/:name/data/:rowIdeditorUpdate a row
DELETE/api/custom-tables/by-name/:name/data/:rowIdeditorDelete a row
bash
# An ERP pushes a lot record into the "lots" table
curl -s -X POST https://scada.local/api/custom-tables/by-name/lots/data \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"row":{"lot_number":"L-24117","part_number":"BRK-220","qty_planned":1200}}'

Body shapes, column types and per-table access rules: Data Tables access.

Run logic on the appliance ​

MethodPathMinimum rolePurpose
POST/api/named-queries/:idOrName/executeany (the query's own role setting decides)Run a named query: { "params": { … } }
POST/api/workflows/:id/executeeditorStart a workflow: { "params": { … }, "wait": false }
POST/api/scripts/:id/executeeditorRun a script now
POST/api/schedules/:id/runeditorRun a scheduled task now
bash
curl -s -X POST https://scada.local/api/named-queries/lot_status/execute \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"params":{"lot":"L-24117"}}'

A workflow started without "wait": true returns at once with { "status": "running" }; with wait the request stays open until the workflow finishes and returns its result. Named queries are the safe way to give an external system SQL access — the statement is fixed by whoever wrote it and only the parameters come from the caller. See Named Queries.

Production data ​

OEE, downtime, quality, work orders and recipes each have their own endpoints — for example /api/oee, /api/downtime, /api/work-orders. The request bodies and roles are documented with each feature: OEE, Work Orders.

A complete Python client ​

python
import time, requests

BASE = "https://scada.local/api"
session = requests.Session()
session.verify = "/etc/ssl/certs/scada-local.pem"   # or False on a lab bench

def login():
    r = session.post(f"{BASE}/auth/login", json={"email": "mes-link@plant.com", "password": "S3cure-Pass"})
    r.raise_for_status()
    body = r.json()
    if body["data"].get("mfaRequired"):
        raise RuntimeError("Use an integration account without two-factor")
    session.headers["Authorization"] = "Bearer " + body["data"]["tokens"]["token"]
    return body["data"]["tokens"]["refreshToken"]

def call(method, path, **kw):
    r = session.request(method, BASE + path, timeout=10, **kw)
    if r.status_code == 401:            # expired: sign in again and retry once
        login()
        r = session.request(method, BASE + path, timeout=10, **kw)
    body = r.json()
    if not body.get("success"):
        raise RuntimeError(f"{r.status_code}: {body.get('error')}")
    return body["data"]

login()
while True:
    for v in call("GET", "/tags/live/3"):
        if v["quality"] == "good":
            print(v["tagPath"], float(v["value"]) if v["dataType"] == "float" else v["value"])
    time.sleep(5)

From a script on the appliance ​

Prefer the helpers — tags.write(...), alarms.acknowledge(...), history.query(...) — which need no token. When you do need an endpoint without a helper:

javascript
const session = auth.login('automation@plant.com', 'S3cure-Pass');   // local accounts only; null on failure
const hdr = { Authorization: 'Bearer ' + session.tokens.token };
const res = await http.get('http://localhost:3001/api/alarms/history?limit=20', hdr);
log('Alarms in history: ' + res.meta.total);

http.* returns the parsed JSON envelope and times out after 10 seconds. Keep passwords in Secrets rather than in the script text.

See also ​

Scada.Ink Help Center