This plugin sends metrics to a REX IoT Cloud API endpoint using JSON-RPC 2.0
method calls. It validates incoming metrics against configurable
data table schemas, applies field-name mappings, fills datetime columns from
the Telegraf metric time when not present in the data, supports virtual
columns (?) that participate in processing but are stripped before sending,
and conditional nulling (null_if) rules that set fields to null based on
other columns' values.
Three write strategies are available:
insertData; all rows in a group succeed orinsertDataByOne; rows are written individually andinsertData first; on failure falls back toinsertDataByOne to salvage valid rows from the batch.In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the Telegraf Output Plugin Configuration for available parameters for more details.
## Send metrics to REX IoT Cloud via JSON-RPC API.
[[outputs.rex_iot]]
## JSON-RPC API endpoint URL.
url = "http://localhost/iot-api/www/api/v0/test-1/project_1"
## Authentication credentials included in every JSON-RPC request.
api_key = ""
secret_key = ""
## HTTP request timeout.
# timeout = "5s"
## Error handling strategy for failed API requests.
## "retry" - return an error so Telegraf retries the batch
## "drop" - log the error and discard the data
# error_handling = "retry"
## Write strategy controls which JSON-RPC method is used:
## "atomic" - uses insertData; all rows succeed or fail together (default)
## "robust" - uses insertDataByOne; rows are written individually and
## rejected rows are reported back to Telegraf with per-row
## error messages
## WARNING: "robust" is significantly slower. Use it only when the data source
## is very unstable or values frequently fall outside the allowed min/max range
## configured on the IoT Cloud.
# write_strategy = "atomic"
## Tell the API to silently ignore rows with duplicate timestamps.
# ignore_duplicit = true
## If true, skip sending data when the object is disconnected.
# omit_disconnected = false
## Data table schema definitions. Each data table has an ID and a schema
## mapping column names to type descriptors.
##
## Supported base types (matching REX IoT Cloud data types):
## Integer: small_int, int, big_int
## Float: real, double
## Boolean: bool
## String: varchar, text
## Date/Time: date, time, time_timezone, datetime, datetime_timezone
## Reference: object_reference, enum_reference (stored as integer IDs)
## Bitfield: numeric_bitfield (unsigned int), bitfield (bit string)
## JSON: json_binary (parsed into native JSON in the payload)
## Binary: byte_array (base64 encoded string)
## Network: ip_address, uuid
##
## Append [] for array types, e.g. "double[]".
## Append ! to mark a column as required, e.g. "double!".
## Append ? to mark a column as virtual, e.g. "bool?".
## Modifiers can be combined with []: "double[]!", "int[]?".
## ! and ? are mutually exclusive — a virtual column cannot be required.
##
## Virtual columns (?) participate in field mapping, type conversion, and
## null_if evaluation, but are removed from the row before sending to the
## API. They are useful as control signals (e.g. validity flags) that
## should not be persisted in the cloud.
##
## Datetime columns (datetime, datetime_timezone) that are not present in
## the incoming data are automatically filled from the Telegraf metric time.
# [[outputs.rex_iot.data_table]]
# id = 1
# [outputs.rex_iot.data_table.schema]
# "temperature" = "double!"
# "humidity" = "double"
# "humidity_valid" = "bool?" # virtual — used by null_if, not sent to cloud
# "time" = "datetime!"
## Object definitions bind API-facing (object_table_id, object_id) pairs
## to a local data_table_id for schema validation, and define optional
## field-name mappings from Telegraf metric fields to data table columns.
##
## Metrics are matched to objects via the "object_id" and
## "object_table_id" tags.
# [[outputs.rex_iot.object]]
# object_table_id = 1
# object_id = 101
# data_table_id = 1
# [outputs.rex_iot.object.mapping]
# "temperature" = "$.task.AND:U1"
# "humidity" = "$.task.AND:U2"
## Optional default values for columns.
# [outputs.rex_iot.object.default_values]
# "humidity" = 0.0
# "status" = "unknown"
## Optional null_if rules for conditional nulling.
# [[outputs.rex_iot.object.null_if]]
# field = "humidity"
# control_field = "humidity_valid"
# values = [false]
Incoming metrics must carry two tags:
object_table_id — the API-facing table identifierobject_id — the API-facing object identifierThese may also be set by the json_v2 input parser. The plugin matches each
metric to a configured [[outputs.rex_iot.object]] entry using this tag pair.
If an object defines an optional [mapping] section, the keys are target column names
and the values are input field names from the Telegraf metric. For example,
"temperature" = "$.task.AND:U1" maps the input field $.task.AND:U1 to the
temperature column.
Fields without a mapping entry are matched by column name directly.
Each object may define an optional [default_values] section mapping column
names to values. Values may be strings, integers, or floats — they are
type-cast to the column's declared type just like regular metric fields.
When a metric does not provide a field for a configured column and that
column has a default value, the default is used instead of skipping (optional)
or dropping the row (required).
[[outputs.rex_iot.object]]
object_table_id = 1
object_id = 101
data_table_id = 1
[outputs.rex_iot.object.mapping]
"temp" = "$.task.AND:U1"
"status" = "$.task.SW:Out"
[outputs.rex_iot.object.default_values]
"status" = 100
"hum" = 5.2
Default values are validated at startup:
Priority order when resolving a column value:
default_values entry for the column.datetime / datetime_timezone columns areColumns marked with ? (e.g. "bool?") are virtual — they go through
the full pipeline (field mapping, type conversion, default value fill) but
are removed from the row before it is sent to the API. This makes them
useful as control signals for null_if rules without persisting them in the
cloud.
Virtual and required (!) modifiers are mutually exclusive. A column that
is never sent cannot be required.
[[outputs.rex_iot.data_table]]
id = 1
[outputs.rex_iot.data_table.schema]
"temp" = "double!"
"hum" = "double"
"hum_invalid" = "bool?" # virtual — participates in null_if, not sent
"time" = "datetime!"
Each object may define one or more [[null_if]] rules. A rule specifies:
field — the target column to null (must exist, must not be required)control_field — the column whose value is inspected (must exist)values — a list of values; if control_field equals any of them, fieldValues are cast to the control_field's declared type at startup, so runtime
comparison is exact. Multiple rules targeting the same field are OR-ed:
if any rule matches, the field is nulled.
Processing order inside transformGroup():
null_if rules (control values are already cast)Because virtual column removal happens after null_if evaluation,
virtual columns can serve as control fields.
[[outputs.rex_iot.object]]
object_table_id = 1
object_id = 101
data_table_id = 1
[outputs.rex_iot.object.mapping]
"temp" = "$.task.AND:U1"
"hum" = "$.task.AND:U2"
"hum_invalid" = "$.task.HUM_FLAG"
# If hum_invalid == false → hum is set to null
[[outputs.rex_iot.object.null_if]]
field = "hum"
control_field = "hum_invalid"
values = [false]
# If status == 1 or 2 or 825 → msg is set to null
[[outputs.rex_iot.object.null_if]]
field = "msg"
control_field = "status"
values = [1, 2, 825]
Validation at startup:
field must reference an existing, non-required column.control_field must reference an existing column.values must be castable to the control_field's type.Each row is validated against the data table schema referenced by the object's
data_table_id:
Whitelist filtering — only fields matching a defined column are kept.
Type casting — values are converted to the declared type. The full set
of supported data types and their conversions:
| Config type | PostgreSQL type | Go / JSON wire representation |
|---|---|---|
object_reference |
int |
int64 / uint64 (JSON number) — ID of the referenced object |
enum_reference |
int |
int64 / uint64 (JSON number) — ID of the enum value |
bool |
boolean |
bool (JSON boolean) |
small_int |
smallint |
int64 / uint64 (JSON number) |
int |
int |
int64 / uint64 (JSON number) |
big_int |
bigint |
int64 / uint64 (JSON number) |
real |
real |
float64 (JSON number) |
double |
double precision |
float64 (JSON number) |
varchar |
character varying |
string (JSON string) |
text |
text |
string (JSON string) |
numeric_bitfield |
int |
uint64 (JSON number) — unsigned integer bitfield |
bitfield |
bit varying |
uint64 (JSON number) — variable-length bit string |
json_binary |
jsonb |
native JSON — value is parsed and embedded as a JSON object/array |
date |
date |
string (JSON string, e.g. "2026-01-15") |
time |
time |
string (JSON string, e.g. "14:30:00") |
time_timezone |
timetz |
string (JSON string, e.g. "14:30:00+02") |
datetime |
timestamp |
float64 / string — "timestamp(sec.usec)" / "2026-01-15 14:30:00.000000" format |
datetime_timezone |
timestamptz |
float64 / string — "timestamp(sec.usec)" / "2026-01-15 14:30:00+02" format |
byte_array |
bytea |
string (JSON string, base64 encoded) |
ip_address |
inet |
string (JSON string, e.g. "192.168.1.1") |
uuid |
uuid |
string (JSON string, e.g. "550e8400-e29b-...") |
Integer types use int64 by default but fall back to uint64 for large
positive values that overflow int64 (e.g. Telegraf's native uint64
fields). Both are valid JSON numbers.
Datetime types accept numeric values (epoch seconds), numeric strings,
time.Time objects, or strings. Numeric strings "1771510081.512123"
are used as epoch seconds. Other strings are passed through unchanged.
varchar is functionally identical to text — both pass values as
strings without length validation.
Array types (e.g. double[]) expect a JSON-encoded string (e.g.
"[1.1, 2.2]").
Note: The plugin does not validate value ranges or sizes (e.g.
small_int range, varchar length). Values are only type-cast; the cloud
API performs full constraint validation.
Required fields — columns marked with ! must be present after mapping
and type casting. If any required column is missing or fails casting, the
entire row is discarded with a debug log message.
Datetime auto-fill — datetime and datetime_timezone columns that
are not present in the data are automatically populated from the Telegraf
metric's internal time using the "timestamp(sec.usec)" format.
null_if evaluation — conditional null rules are applied after all
columns are resolved and cast.
Virtual column removal — columns marked with ? are stripped from
the row before it is sent to the API.
For each unique (object_table_id, object_id) group, the plugin sends one
HTTP POST request with a JSON-RPC 2.0 payload. The method name depends on
the write_strategy setting.
insertData){
"jsonrpc": "2.0",
"id": "1771510081",
"method": "insertData",
"params": {
"apiKey": "...",
"secretKey": "...",
"objectTableId": 1,
"objectId": 101,
"ignoreDuplicit": true,
"omitDisconnected": false,
"data": [
{
"temp": 22.5,
"status": 1,
"time": "timestamp(1771510081.928456)"
}
]
}
}
The API responds with "result": true on success. If the result is not true
or the response contains an error, all metrics in the group are treated as
failed.
insertDataByOne)The request format is identical but uses method "insertDataByOne". The API
responds with a result containing a rejectedRows map:
{
"jsonrpc": "2.0",
"id": "1771510081",
"result": {
"rejectedRows": {
"0": "Missing column value1.",
"2": "Bad value format for column temp."
}
}
}
Each key in rejectedRows is a zero-based index into the data array, and
the value is the error message for that row. Rows not listed in rejectedRows
were written successfully. Rejected metrics are reported back to Telegraf so
they are not retried.
Warning: The
robuststrategy is significantly slower because the API
processes rows individually. Use it only when the data source is very
unstable or values frequently fall outside the allowed min/max range
configured on the IoT Cloud. For stable sources, preferatomic.
robust_fallback)First tries the fast insertData call. If it fails (the API does not return
"result": true), the plugin falls back to insertDataByOne to salvage the
valid rows from the batch.
This is the best choice when the data is mostly valid but occasionally
contains minor out-of-range values (e.g. a sensor briefly exceeding the
configured min/max on the IoT Cloud). In the common case only one fast call
is made; the slow fallback is triggered only when needed.
Warning: For data that is frequently invalid,
robust_fallbackis
the worst option — both API methods are called on every flush, making
it slower than usingrobustalone. If the source is known to be unstable,
userobustdirectly.
Timestamp values use the format timestamp(<seconds>.<microseconds>).
The error_handling setting controls what happens when an API call fails:
"retry" (default) — the error is returned to Telegraf, which will retry"drop" — the error is logged and data is discarded (matching the behaviorIn all three strategies, metrics that fail local validation (missing required
fields, type mismatches, missing tags, unknown objects) are permanently
rejected — they are not retried because they would fail again.
## Send metrics to REX IoT Cloud via JSON-RPC API.
[[outputs.rex_iot]]
## JSON-RPC API endpoint URL.
url = "http://localhost/iot-api/www/api/v0/test-1/project_1"
## Authentication credentials included in every JSON-RPC request.
api_key = ""
secret_key = ""
## HTTP request timeout.
# timeout = "5s"
## Error handling strategy for failed API requests.
## "retry" - return an error so Telegraf retries the batch
## "drop" - log the error and discard the data
# error_handling = "retry"
## Write strategy controls which JSON-RPC method is used:
## "atomic" - uses insertData; all rows succeed or fail
## together (default)
## "robust" - uses insertDataByOne; rows are written
## individually and rejected rows are reported
## back to Telegraf with per-row error messages
## "robust_fallback" - tries insertData first; if it fails, falls
## back to insertDataByOne to salvage valid rows
## WARNING: "robust" is significantly slower. Use it only when the data
## source is very unstable or values frequently fall outside the allowed
## min/max range configured on the IoT Cloud.
## "robust_fallback" is ideal when data is mostly valid but occasionally
## has minor out-of-range values. For mostly-bad data it is the worst
## option as both methods are called every time.
# write_strategy = "atomic"
## Tell the API to silently ignore rows with duplicate timestamps.
# ignore_duplicit = true
## If true, skip sending data when the object is disconnected.
# omit_disconnected = false
## Data table schema definitions. Each data table has an ID and a schema
## mapping column names to type descriptors.
##
## Supported base types (matching REX IoT Cloud data types):
## Integer: small_int, int, big_int
## Float: real, double
## Boolean: bool
## String: varchar, text
## Date/Time: date, time, time_timezone, datetime, datetime_timezone
## Reference: object_reference, enum_reference (stored as integer IDs)
## Bitfield: numeric_bitfield (unsigned int), bitfield (unsigned int)
## JSON: json_binary (parsed into native JSON in the payload)
## Binary: byte_array (base64 encoded string)
## Network: ip_address, uuid
##
## Append [] for array types, e.g. "double[]".
## Append ! to mark a column as required, e.g. "double!".
## Append ? to mark a column as virtual, e.g. "bool?".
## Modifiers can be combined with []: "double[]!", "int[]?".
## ! and ? are mutually exclusive — a virtual column cannot be required.
##
## Virtual columns (?) participate in field mapping, type conversion, and
## null_if evaluation, but are removed from the row before sending to the
## API. They are useful as control signals (e.g. validity flags) that
## should not be persisted in the cloud.
##
## Note: The plugin does NOT validate value ranges or sizes (e.g. small_int
## range, varchar length). Values are only type-cast; the cloud API
## performs full constraint validation.
##
## Datetime columns (datetime, datetime_timezone) that are not present in
## the incoming data are automatically filled from the Telegraf metric time.
# [[outputs.rex_iot.data_table]]
# id = 1
# [outputs.rex_iot.data_table.schema]
# "temperature" = "double!"
# "humidity" = "double"
# "humidity_valid" = "bool?" # virtual — used by null_if, not sent to cloud
# "time" = "datetime!"
## Object definitions bind API-facing (object_table_id, object_id) pairs
## to a local data_table_id for schema validation, and define optional
## field-name mappings from Telegraf metric fields to data table columns.
## Mapping keys are target column names; values are Telegraf input field names.
## Fields without a mapping entry are matched by column name directly.
##
## Metrics are matched to objects via the "object_id" and
## "object_table_id" tags.
# [[outputs.rex_iot.object]]
# object_table_id = 1
# object_id = 101
# data_table_id = 1
# [outputs.rex_iot.object.mapping]
# "temperature" = "$.task.AND:U1"
# "humidity" = "$.task.AND:U2"
## Optional default values for columns. If a metric does not contain a
## mapped (or direct) field for a column that has a default configured,
## the default value is used instead. Values may be strings, integers,
## or floats and are type-cast at runtime just like regular field values.
## Keys are column names (not input field names). Invalid keys or
## non-castable values are rejected at startup.
# [outputs.rex_iot.object.default_values]
# "humidity" = 0.0
# "status" = "unknown"
## Optional null_if rules for conditional nulling. Each rule specifies a
## target field, a control_field whose value is inspected, and a list of
## values. If the control_field's cast value equals any entry in values,
## the target field is removed from the row and set to null (removed and set to null by database on insert).
##
## Constraints:
## - field must exist in the data table and must NOT be required (!).
## - control_field must exist in the data table.
## - values are cast to the control_field's type at startup.
## - Multiple null_if entries for the same field are OR-ed: if any
## rule matches, the field is nulled.
## - Evaluation happens after mapping/casting but before virtual
## column removal, so virtual columns can serve as control_fields.
# [[outputs.rex_iot.object.null_if]]
# field = "humidity"
# control_field = "humidity_valid"
# values = [false]