HTTP Payload Parsing and Telemetry Writing
Applies to ThinkLink 2.00.099. Both API families perform real ingestion with side effects: activity, latest values, history, forwarding, triggers, and related-device notifications may change. Simulate Send also processes real data; it is not a dry run. Confirm the tenant, devices, and saved model bindings before submission.
Choose an API
| Item | Raw payload parsing | Parsed telemetry writing |
|---|---|---|
| Single / batch | parse-payload / batch-parse-payload | write-telemetry / batch-write-telemetry |
| Input | data.msg is the raw JSON object passed to the script | data.msg contains a model identifier and a telemetry data object |
| Target model script | Runs selected bound models | Skips the target device parser |
| Model location | Top-level thing_model_ids / thing_model_id_names | Per-message msg.thing_model_id / msg.thing_model_id_name |
return_data: true | This invocation's raw script output | This write's merged telemetry snapshot |
All requests use POST, JSON, and the existing token header. Tenant routes begin with /thinklink/device/; system routes begin with /thinklink/system/device/, followed by an operation above. Tenant routes derive the tenant from the token; an optional tenant must match it. System routes require SYSADMIN and an explicit top-level tenant containing the target tenant_code. Supplying a tenant does not grant access.
Parameters and model selection
- Single
datais an object; batchdatais a non-empty array. Each entry has a non-empty stringeuiand an objectmsg. Each batch belongs to one tenant; duplicate EUIs are allowed and processed in input order. - Parsing passes
msgdirectly to the model script without another eui/data/tenant wrapper, extra JSON.parse, or injected MQTT topic. It must not be a string, array, or null; business fields remain intact. - Parsing selectors are top-level arrays:
thing_model_idscontains database ID strings;thing_model_id_namescontainsid_namevalues, not display names. Both together form a deduplicated union executed in device binding order. Omit both to run all bound models. Empty arrays or blank identifiers are invalid. Models must be bound and usable by the tenant; each name must uniquely match an available bound model. Selection neither changes bindings nor propagates to related virtual-device parsing. - Putting selectors inside a
dataentry returns 400. A batch shares the same selectors and every device must satisfy them. Simulate Send lists only saved bindings and submits top-levelthing_model_ids. - Writing requires at least one non-empty
msg.thing_model_id_nameormsg.thing_model_id; when both are present they must identify the same model. Name lookup prefers the tenant then PUBLIC, with permission and binding checks. Top-level parsing selectors are rejected with 400. - Writing requires an existing device and bound model, and never creates or binds them.
msg.datamust be an object, including an empty object. Field values preserve0,false,null, and nested objects. The wholemsg.datamust not be null, an array, or a string. - Writing merges latest telemetry by model, retains omitted fields, and updates telemetry
update_timeand device activity using current server time. History followsreal_time_storage; disabling history still updates latest values and notifications. Existing forwarding, triggers, related-device and protocol notifications apply; related devices can still run their own parsers. - This is not a historical backfill, attribute-update, or arbitrary RPC endpoint. Sending a timestamp does not turn it into backfill. Ordinary telemetry writes do not change configuration time.
optionis optional;option.return_datadefaults to false and accepts only a boolean, not the string"true".
Eight request examples
Set TKL_BASE in your own terminal (for example https://your-thinklink-host, without /thinklink) and TKL_TOKEN. These commands submit real data. Replace EUIs, saved model identifiers, tenant and the appropriately authorized token first. The example temperature model must already exist and be bound; parsing output depends on its script. Parsing may instead use top-level thing_model_ids: ["actual-model-id"]; writing uses thing_model_id inside msg.
Tenant side · parse-payload
curl --request POST "$TKL_BASE/thinklink/device/parse-payload" \
--header "token: $TKL_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"data": {
"eui": "00112233",
"msg": {
"temperature": 23.5
}
},
"thing_model_id_names": [
"temperature"
],
"option": {
"return_data": true
}
}'System side (SYSADMIN) · parse-payload
curl --request POST "$TKL_BASE/thinklink/system/device/parse-payload" \
--header "token: $TKL_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"tenant": "tenantA",
"data": {
"eui": "00112233",
"msg": {
"temperature": 23.5
}
},
"thing_model_id_names": [
"temperature"
],
"option": {
"return_data": true
}
}'Tenant side · batch-parse-payload
curl --request POST "$TKL_BASE/thinklink/device/batch-parse-payload" \
--header "token: $TKL_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"data": [
{
"eui": "00112233",
"msg": {
"temperature": 23.5
}
},
{
"eui": "00445566",
"msg": {
"temperature": 24.5
}
}
],
"thing_model_id_names": [
"temperature"
],
"option": {
"return_data": true
}
}'System side (SYSADMIN) · batch-parse-payload
curl --request POST "$TKL_BASE/thinklink/system/device/batch-parse-payload" \
--header "token: $TKL_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"tenant": "tenantA",
"data": [
{
"eui": "00112233",
"msg": {
"temperature": 23.5
}
},
{
"eui": "00445566",
"msg": {
"temperature": 24.5
}
}
],
"thing_model_id_names": [
"temperature"
],
"option": {
"return_data": true
}
}'Tenant side · write-telemetry
curl --request POST "$TKL_BASE/thinklink/device/write-telemetry" \
--header "token: $TKL_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"data": {
"eui": "00112233",
"msg": {
"thing_model_id_name": "temperature",
"data": {
"temperature": 23.5,
"humidity": 60
}
}
},
"option": {
"return_data": true
}
}'System side (SYSADMIN) · write-telemetry
curl --request POST "$TKL_BASE/thinklink/system/device/write-telemetry" \
--header "token: $TKL_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"tenant": "tenantA",
"data": {
"eui": "00112233",
"msg": {
"thing_model_id_name": "temperature",
"data": {
"temperature": 23.5,
"humidity": 60
}
}
},
"option": {
"return_data": true
}
}'Tenant side · batch-write-telemetry
curl --request POST "$TKL_BASE/thinklink/device/batch-write-telemetry" \
--header "token: $TKL_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"data": [
{
"eui": "00112233",
"msg": {
"thing_model_id_name": "temperature",
"data": {
"temperature": 23.5,
"humidity": 60
}
}
},
{
"eui": "00445566",
"msg": {
"thing_model_id_name": "temperature",
"data": {
"temperature": 24.5,
"humidity": 0
}
}
}
],
"option": {
"return_data": true
}
}'System side (SYSADMIN) · batch-write-telemetry
curl --request POST "$TKL_BASE/thinklink/system/device/batch-write-telemetry" \
--header "token: $TKL_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"tenant": "tenantA",
"data": [
{
"eui": "00112233",
"msg": {
"thing_model_id_name": "temperature",
"data": {
"temperature": 23.5,
"humidity": 60
}
}
},
{
"eui": "00445566",
"msg": {
"thing_model_id_name": "temperature",
"data": {
"temperature": 24.5,
"humidity": 0
}
}
}
],
"option": {
"return_data": true
}
}'Responses and failure boundaries
Without returned data, both families respond with {"result":true} after processing. A single returned-data envelope is shown below; the ID and time are illustrative:
{
"result": true,
"data": {
"eui": "00112233",
"results": [
{
"thing_model_id": "1234567890",
"data": {
"telemetry_data": {
"temperature": 23.5,
"humidity": 60,
"update_time": "2026-10-26T00:00:00.000Z"
}
}
}
]
}
}This writing response contains only the written model and its merged snapshot, including retained fields and server time. It is a deep JSON snapshot captured before the first asynchronous wait. Parsing uses the same envelope but each data is the raw script output (object, array, or null), in binding order, not the complete device state. A normal empty output is not a failure. Batch responses use an outer data array in input order, without deduplicating EUIs.
The whole batch is prechecked for format, tenant, devices, model permissions and bindings, then processed in order with a fresh check before each entry. Precheck failure prevents this batch's writes. Execution is not a transaction: it stops at the first error, earlier writes and notifications remain, and the failing entry may already have partial side effects.
Preserve HTTP 400 (parameters, models, processing), 403 (tenant/permission boundary), 404 (missing target device), and AppError message, format, values. An older server with no route can also return 404; inspect its error and version instead of automatically switching to a different write endpoint.
After timeout or an unknown outcome, mark the result as pending confirmation. Inspect latest values, history, notifications, and device feedback before a bounded retry of unfinished work. Do not blindly replay the whole batch: history and notifications can be duplicated. HTTP success does not confirm buffer flush, external notification delivery, or physical device execution.
RPC shares these writing semantics; see writeTelemetry. Local validation, deployment, and device acceptance are separate states; see the 2.00.099 upgrade guide.