Send data from Node-RED

A ready-to-import flow that posts readings to the ingest API, or publishes them over MQTT.

Node-RED is a good fit on an edge PC or gateway: it can read Modbus, S7, EtherNet/IP and OPC UA with community nodes and post the values to InduQT. The flow below runs inject → function → http request → debug.

Set up

  1. Create the site and machines

    Create them in the app or with POST /v1/sites and POST /v1/machines. Set each machine's externalId to the name your source uses (for example B-101), so messages can address it without knowing InduQT ids.

  2. Create an API key

    In Settings → API keys, create a key with the ingest scope, limited to the site the data belongs to. See site-scoped keys.

  3. Give Node-RED the key

    Set INDUQT_API_KEY in the Node-RED process environment, so it is not stored in the flow: docker run -e INDUQT_API_KEY=iq_live_… nodered/node-red, Environment=INDUQT_API_KEY=… in a systemd unit, or export it before node-red starts. The function node reads it with env.get('INDUQT_API_KEY').

  4. Import the flow

    Menu → Import, paste the JSON, Import, then Deploy.

  5. Wire in your tags

    Replace the simulated value in Build reading with your tag, and change B-101 to your machine's externalId. The debug node shows statusCode 200 and accepted for each post.

Flow: post to the ingest API

Node-RED flow: InduQT ingest
[
  {
    "id": "iq_tab_http",
    "type": "tab",
    "label": "InduQT ingest",
    "disabled": false,
    "info": "Posts a reading to the InduQT ingest API every 5 seconds. Set INDUQT_API_KEY in the Node-RED environment."
  },
  {
    "id": "iq_inject",
    "type": "inject",
    "z": "iq_tab_http",
    "name": "Every 5 s",
    "props": [
      {
        "p": "payload"
      }
    ],
    "repeat": "5",
    "crontab": "",
    "once": true,
    "onceDelay": "1",
    "topic": "",
    "payload": "",
    "payloadType": "date",
    "x": 130,
    "y": 100,
    "wires": [
      [
        "iq_build"
      ]
    ]
  },
  {
    "id": "iq_build",
    "type": "function",
    "z": "iq_tab_http",
    "name": "Build reading",
    "func": "// Replace the simulated value with your tag, e.g. msg.payload from an S7, Modbus or OPC UA node.\nconst steamPressure = Number((110 + Math.random() * 5).toFixed(1));\n\nmsg.headers = {\n  'Authorization': 'Bearer ' + env.get('INDUQT_API_KEY'),\n  'Content-Type': 'application/json'\n};\nmsg.payload = {\n  machine: 'B-101',\n  values: { steam_pressure: steamPressure }\n};\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 320,
    "y": 100,
    "wires": [
      [
        "iq_post"
      ]
    ]
  },
  {
    "id": "iq_post",
    "type": "http request",
    "z": "iq_tab_http",
    "name": "POST /v1/ingest",
    "method": "POST",
    "ret": "obj",
    "paytoqs": "ignore",
    "url": "https://api.induqt.ai/v1/ingest",
    "tls": "",
    "persist": false,
    "proxy": "",
    "insecureHTTPParser": false,
    "authType": "",
    "senderr": false,
    "headers": [],
    "x": 530,
    "y": 100,
    "wires": [
      [
        "iq_debug"
      ]
    ]
  },
  {
    "id": "iq_debug",
    "type": "debug",
    "z": "iq_tab_http",
    "name": "InduQT response",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": true,
    "complete": "true",
    "targetType": "full",
    "statusVal": "statusCode",
    "statusType": "msg",
    "x": 750,
    "y": 100,
    "wires": []
  }
]

The function node builds this request:

msg sent to http request
{
  "method": "POST",
  "headers": {
    "Authorization": "Bearer iq_live_…",
    "Content-Type": "application/json"
  },
  "payload": {
    "machine": "B-101",
    "values": {
      "steam_pressure": 112.4
    }
  }
}

Every 5 s is fine for one machine. For many tags, collect them in one function (or a join node) and send a single request with several values, or a readings batch; see the ingest API.

Alternative: publish over MQTT

If the site already has a broker that InduQT subscribes to through an MQTT connector, publish there instead. With topic filter plant/+/telemetry and machineFrom: "topic", topicLevel: 1, the message below lands on machine B-101. Set the broker host, port and credentials in the Plant broker config node after import.

Node-RED flow: InduQT over MQTT
[
  {
    "id": "iq_tab_mqtt",
    "type": "tab",
    "label": "InduQT over MQTT",
    "disabled": false,
    "info": "Publishes readings to plant/B-101/telemetry. Add an InduQT MQTT connector with topic plant/+/telemetry and machineFrom \"topic\", topicLevel 1."
  },
  {
    "id": "iq_mqtt_inject",
    "type": "inject",
    "z": "iq_tab_mqtt",
    "name": "Every 5 s",
    "props": [
      {
        "p": "payload"
      }
    ],
    "repeat": "5",
    "crontab": "",
    "once": true,
    "onceDelay": "1",
    "topic": "",
    "payload": "",
    "payloadType": "date",
    "x": 130,
    "y": 100,
    "wires": [
      [
        "iq_mqtt_build"
      ]
    ]
  },
  {
    "id": "iq_mqtt_build",
    "type": "function",
    "z": "iq_tab_mqtt",
    "name": "Build message",
    "func": "// Replace the simulated value with your tag.\nmsg.topic = 'plant/B-101/telemetry';\nmsg.payload = {\n  ts: new Date().toISOString(),\n  steam_pressure: Number((110 + Math.random() * 5).toFixed(1))\n};\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 320,
    "y": 100,
    "wires": [
      [
        "iq_mqtt_out"
      ]
    ]
  },
  {
    "id": "iq_mqtt_out",
    "type": "mqtt out",
    "z": "iq_tab_mqtt",
    "name": "plant/B-101/telemetry",
    "topic": "",
    "qos": "1",
    "retain": "false",
    "respTopic": "",
    "contentType": "application/json",
    "userProps": "",
    "correl": "",
    "expiry": "",
    "broker": "iq_broker",
    "x": 540,
    "y": 100,
    "wires": []
  },
  {
    "id": "iq_broker",
    "type": "mqtt-broker",
    "name": "Plant broker",
    "broker": "broker.example.com",
    "port": "8883",
    "tls": "",
    "clientid": "",
    "autoConnect": true,
    "usetls": true,
    "protocolVersion": "4",
    "keepalive": "60",
    "cleansession": true,
    "autoUnsubscribe": true,
    "birthTopic": "",
    "birthQos": "0",
    "birthPayload": "",
    "birthMsg": {},
    "closeTopic": "",
    "closeQos": "0",
    "closePayload": "",
    "closeMsg": {},
    "willTopic": "",
    "willQos": "0",
    "willPayload": "",
    "willMsg": {},
    "userProps": "",
    "sessionExpiry": ""
  }
]

Troubleshooting

Troubleshooting
SymptomLikely cause and fix
401 with Bearer undefinedINDUQT_API_KEY is not in the Node-RED environment. Set it and restart Node-RED; redeploying is not enough.
400 Bad RequestThe body is not JSON or values are not numbers. Check that the function sets msg.payload to an object and the http request node returns a parsed object.
404 or rejected readingsThe machine name is not an externalId at the key's site. Use a site-scoped key, add siteId to the payload, or set autoCreate: true.
Certificate errors behind a proxyA TLS-inspecting proxy needs its CA trusted by Node.js (NODE_EXTRA_CA_CERTS) or a TLS config on the http request node.
Gaps after network outagesBuffer messages while offline (for example with node-red-contrib-queue-gate) and resend: repeated readings overwrite, so resending is safe.