Ingest payload schema
The JSON body a device or stream sends, field by field, with the limits the ingest service enforces.
A device or a stream sends its readings as one JSON body per request. This page lists every field the ingest service accepts, and the limits it enforces on each one.
Device payload#
| Field | Type | Required | Description |
|---|---|---|---|
| timestamp | string | No | ISO 8601. Defaults to the time of arrival. Must be within one hour of now. |
| data | object | Yes | Keys map to the device type's metric names or custom keys. Values are number, string or boolean. |
| metadata | object | No | Free-form. Not used for alerts or charts. |
| status | online | offline | error | No | The device reporting its own state. Omit it unless the device knows something the reading does not show. |
Stream payload#
Same as a device payload without status. A stream never reports online, offline or error.
| Field | Type | Required | Description |
|---|---|---|---|
| timestamp | string | No | ISO 8601. Defaults to the time of arrival. |
| data | object | Yes | Keys map to the stream's own field definitions, set when the stream was created. This is not the device type metric registry. |
| metadata | object | No | Free-form. Not used for alerts or charts. |
Limits#
These bound the whole payload, whether a key is a declared field or a custom one.
| Limit | Value | Description |
|---|---|---|
| data keys | 100 | Maximum number of keys in the data object. |
| key length | 100 | Maximum characters in any data or metadata key. |
| string value length | 1000 | Maximum characters in a string value. |
| metadata keys | 50 | Maximum number of keys in the metadata object. |
| metadata size | 16 KB | Maximum serialised size of the metadata object. |
| nesting depth | 32 | Maximum object nesting Logable walks before it rejects the payload. |
A custom key is any key in data that the device type does not declare. Custom keys have their own, tighter limits. These limits apply to devices only. A stream has no declared field set to compare against, so every key in a stream payload is checked against the payload limits above and nothing else.
| Limit | Value | Description |
|---|---|---|
| custom keys | 20 | Maximum number of custom (unregistered) keys in one payload. |
| key length | 64 | Maximum characters in a custom key. |
| string value length | 256 | Maximum characters in a custom key's string value. |
Example#
# Raw Meal Silo - Quick Test (curl)
# Device Type: silo | Device ID: <DEVICE_ID>
#
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
curl -X POST "https://ingest.logable.app/ingest/<DEVICE_ID>" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <DEVICE_TOKEN>" \
-d '{"timestamp":"2026-07-01T10:00:00.000Z","status":"online","data":{"weight":18750.5,"status":"filling"}}'"""
Raw Meal Silo - Logable HTTP Integration (Python)
Device Type: silo
Device ID: <DEVICE_ID>
Installation: pip install requests
"""
import requests
from datetime import datetime, timezone
API_ENDPOINT = "https://ingest.logable.app/ingest/<DEVICE_ID>"
API_TOKEN = "<DEVICE_TOKEN>"
def send_data():
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
payload = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"status": "online",
"data": {
"weight": 18750.5,
"status": "filling"
}
}
response = requests.post(
API_ENDPOINT,
json=payload,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {API_TOKEN}"
},
timeout=10
)
response.raise_for_status()
print(f"Data sent successfully: {response.status_code}")
if __name__ == "__main__":
send_data()/**
* Raw Meal Silo - Logable HTTP Integration (TypeScript)
* Device Type: silo
* Device ID: <DEVICE_ID>
*
* Runtime: Node.js 18+ or modern browser (uses built-in fetch)
*/
const API_ENDPOINT = "https://ingest.logable.app/ingest/<DEVICE_ID>";
const API_TOKEN = "<DEVICE_TOKEN>";
async function sendData(): Promise<void> {
// The "data" fields shown are this device type's registered metrics.
// Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
// Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
// Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
// Custom values appear on dashboards via a widget's "Show Custom Data" option.
const payload = {
timestamp: new Date().toISOString(),
status: "online",
data: {
"weight": 18750.5,
"status": "filling"
},
};
const response = await fetch(API_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_TOKEN}`,
},
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
console.log("Data sent successfully:", response.status);
}
sendData();#!/bin/bash
# Raw Meal Silo - Logable HTTP Integration (Bash)
# Device Type: silo
# Device ID: <DEVICE_ID>
API_ENDPOINT="https://ingest.logable.app/ingest/<DEVICE_ID>"
API_TOKEN="<DEVICE_TOKEN>"
# NOTE: the heredoc below is pure JSON, so it cannot contain comments.
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
# Quoted heredoc delimiter: '$' in metric values is taken literally.
# "timestamp" is optional - the platform stamps arrival time when omitted.
read -r -d '' PAYLOAD <<'EOF'
{
"status": "online",
"data": {
"weight": 18750.5,
"status": "filling"
}
}
EOF
HTTP_CODE=$(curl -s -w "%{http_code}" -o /dev/null \
-X POST "${API_ENDPOINT}" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${API_TOKEN}" \
-d "${PAYLOAD}")
if [ "${HTTP_CODE}" -ge 200 ] && [ "${HTTP_CODE}" -lt 300 ]; then
echo "Data sent successfully (HTTP ${HTTP_CODE})"
exit 0
else
echo "Error sending data (HTTP ${HTTP_CODE})"
exit 1
fi<#
.SYNOPSIS
Raw Meal Silo - Logable HTTP Integration (PowerShell)
Device Type: silo
Device ID: <DEVICE_ID>
#>
$ApiEndpoint = "https://ingest.logable.app/ingest/<DEVICE_ID>"
$ApiToken = "<DEVICE_TOKEN>"
function Send-DeviceData {
$Timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
# The "data" fields shown are this device type's registered metrics.
# Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
# Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
# Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
# Custom values appear on dashboards via a widget's "Show Custom Data" option.
$Payload = @{
timestamp = $Timestamp
status = "online"
data = @{
weight = 18750.5
status = 'filling'
}
} | ConvertTo-Json -Depth 3
$Headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $ApiToken"
}
try {
$Response = Invoke-RestMethod `
-Uri $ApiEndpoint `
-Method Post `
-Headers $Headers `
-Body $Payload `
-TimeoutSec 10
Write-Host "Data sent successfully" -ForegroundColor Green
return $Response
} catch {
Write-Error "Error: $($_.Exception.Message)"
throw
}
}
Send-DeviceData/*
* Raw Meal Silo - Logable HTTP Integration (C / libcurl)
* Device Type: silo
* Device ID: <DEVICE_ID>
*
* Install: sudo apt-get install libcurl4-openssl-dev
* Compile: gcc -o device_client device_client.c -lcurl
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <curl/curl.h>
#define API_ENDPOINT "https://ingest.logable.app/ingest/<DEVICE_ID>"
#define API_TOKEN "<DEVICE_TOKEN>"
/* Example metric payload - CUSTOMIZE DATA HERE */
static const char *DATA_JSON =
"{\"weight\":18750.5,\"status\":\"filling\"}";
int main(void) {
CURL *curl;
CURLcode res;
struct curl_slist *headers = NULL;
time_t now = time(NULL);
char timestamp[64];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
/* Build JSON payload - CUSTOMIZE DATA HERE */
/*
* The "data" fields shown are this device type's registered metrics.
* Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
* Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
* Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
* Custom values appear on dashboards via a widget's "Show Custom Data" option.
*/
/* Metric data is passed as a %s argument (never spliced into the
* format string), so '%' characters in values stay intact. */
char payload[1024];
snprintf(payload, sizeof(payload),
"{\"timestamp\": \"%s\", \"status\": \"online\", \"data\": %s}",
timestamp, DATA_JSON);
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, API_ENDPOINT);
headers = curl_slist_append(headers, "Content-Type: application/json");
char auth_header[256];
snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", API_TOKEN);
headers = curl_slist_append(headers, auth_header);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "Request failed: %s\n", curl_easy_strerror(res));
} else {
printf("Data sent successfully\n");
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}/*
* Raw Meal Silo - Logable HTTP Integration (Arduino / ESP32)
* Device Type: silo
* Device ID: <DEVICE_ID>
*
* Libraries: WiFi, HTTPClient, ArduinoJson
* Board: ESP32 or ESP8266
*/
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_ENDPOINT = "https://ingest.logable.app/ingest/<DEVICE_ID>";
const char* API_TOKEN = "<DEVICE_TOKEN>";
void setup() {
Serial.begin(115200);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" Connected!");
}
void sendMetrics() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi not connected");
return;
}
HTTPClient http;
http.begin(API_ENDPOINT);
http.addHeader("Content-Type", "application/json");
char authHeader[256];
snprintf(authHeader, sizeof(authHeader), "Bearer %s", API_TOKEN);
http.addHeader("Authorization", authHeader);
/* Build JSON payload - CUSTOMIZE DATA HERE */
JsonDocument doc;
doc["status"] = "online";
doc.createNestedObject("data");
doc["data"]["weight"] = 18750.5;
doc["data"]["status"] = "filling";
// The "data" fields shown are this device type's registered metrics.
// Optionally add your OWN custom fields to "data" (e.g. "totalizer": 1284300.0).
// Custom keys: start with a letter; letters/digits/underscores; max 64 chars.
// Up to 20 custom fields; values: number, string (max 256 chars) or boolean.
// Custom values appear on dashboards via a widget's "Show Custom Data" option.
String payload;
serializeJson(doc, payload);
int httpCode = http.POST(payload);
if (httpCode > 0) {
Serial.printf("Data sent (HTTP %d)\n", httpCode);
} else {
Serial.printf("HTTP error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
void loop() {
sendMetrics();
delay(10000); /* Send every 10 seconds */
}Keys that are rejected#
A key named __proto__, constructor or prototype, at any depth in data or metadata, is rejected. These names can corrupt a JavaScript object's prototype, so the ingest service refuses them outright.