# Ingest payload schema (https://logable.app/docs/reference/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 [#device-payload]

Table: see https://logable.app/docs/reference/ingest-payload-schema#device-payload

## Stream payload [#stream-payload]

Same as a device payload without `status`. A stream never reports online, offline or error.

Table: see https://logable.app/docs/reference/ingest-payload-schema#stream-payload

## Limits [#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 [#example]

#### cURL

```bash
# 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"}}'
```

#### Python

```python
"""
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()
```

#### TypeScript

```typescript
/**
 * 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();
```

#### Bash

```bash
#!/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
```

#### PowerShell

```powershell
<#
.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
```

#### C (libcurl)

```c
/*
 * 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;
}
```

#### Arduino / ESP32

```cpp
/*
 * 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 [#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.

## Read next [#read-next]

* [Send readings over HTTP](/docs/integrate/rest-api)
* [Send a field the device type does not define](/docs/integrate/custom-data-points)
