Payload rejected

HTTP 422 — sanity checks failed

You are getting 422 out_of_range or 422 unknown_kind on ingest. The sanity checker at the ingest boundary refuses physically implausible values before they reach storage.

"Out of range"

The ingest endpoint enforces per-kind bounds — see the table on POST /v1/ingest. Common offenders:

  • Temperature reported in Kelvin or Fahrenheit. A sensor saying 294 (K) is rejected because we treat the value as °C. Convert at the device.
  • Battery percent vs battery volts mixed. Shelly Gen1 reports percent; Gen3 reports volts. If your device-side logic sometimes emits 3.92 and sometimes 92, the sanity check will trip on the battery channel.
  • NaN, Infinity, null in JSON. RFC 8259 forbids these as JSON numbers; we 400 with bad_json, not 422. Stringify them yourself or drop them.
  • Pressure in Pa instead of hPa. Standard sea-level pressure is ~1013 hPa = 101 325 Pa. The latter trips the sanity ceiling of 1100. Divide by 100 at the device.

"Unknown kind"

Only the kinds in POST /v1/ingest are accepted. If you have a sensor reporting something exotic (TVOC, PM2.5, soil moisture), you have three options:

  1. Map to the closest existing kind + use the label field. A soil moisture sensor reporting 0–100 % can use humidity with label = "soil". The display unit is the same; only the contextual interpretation changes.
  2. Map to a generic numeric kind. We support voltage and current as the catch-alls for "analog reading"; if your sensor's value can be reasonably expressed as one of these, use it.
  3. Open a ticket. New kinds are added on request; we will not force a roundabout mapping if your sensor is genuinely a new kind. So far we have added co2 (2026-04) on request from a school pilot.

"Out of range" but the reading is honest

If the value is physically valid but outside our sanity bounds — e.g. a cryogenic freezer reporting –90 °C, or a power meter on a 100 kW industrial circuit — open a ticket. We will widen the bounds for your account. We do not silently expand them for everyone because the bound exists to catch real device failures (a corroded thermistor reading +200 °C, an ADC bit-flip).

How to handle 422 on the device

Do not retry a 422. The same payload will be rejected again. Log locally, drop the reading, continue with the next cycle. If your device buffers, do not pile up rejected payloads in the buffer; the buffer will fill with garbage and block honest readings.

For ESPHome devices, the recommended pattern is to apply a filter on the sensor before sending:

sensor:
  - platform: bme280_i2c
    temperature:
      filters:
        - throttle: 60s
        - clamp:
            min_value: -40
            max_value: 80

Out-of-bounds physical readings become "ignore this cycle" client-side rather than "argue with the server".

Audit trail

A 422 is not recorded in the audit trail — it is rejected before becoming a measurement. If you need to know that a sensor failed during a period (for HACCP), look at the per-channel gaps endpoint:

GET /v1/channels/chn_…/gaps?from=…&to=…

A continuous run of 422s shows up as a gap in the data, which is audit-visible.