Skip to content

Readings

The readings endpoint accepts HVAC sensor data from your thermostat or Home Assistant integration. Push a reading every 5 minutes (or batch several) so Hungry Machines can learn how your home responds and produce accurate schedules.

Auth: Required — a user access token.

Only three fields are required on each reading: timestamp, indoor_temp, and hvac_state. Everything else is optional — but the more you send, the more accurate your schedules become. Send whatever your device exposes; there’s no penalty for omitting a field you don’t have.

{
"readings": [
{
"timestamp": "2025-11-18T14:30:00+00:00",
"indoor_temp": 72.5,
"hvac_state": "COOL",
"indoor_humidity": 45.0,
"outdoor_temp": 85.0,
"target_temp": 72.0,
"fan_mode": "auto",
"power_watts": 298.0,
"hvac_action": "cooling",
"commanded_hvac_mode": "COOL",
"commanded_fan_mode": "low",
"commanded_setpoint": 71.0,
"indoor_source": "sensor",
"override_active": false,
"room_target": 73.0,
"appliance_id": "11111111-1111-1111-1111-111111111111"
}
],
"client": {
"kind": "hacs",
"version": "3.7.0",
"node_bundle": null,
"ha_version": "2026.8.1"
}
}
Field Type Required Constraints / notes
readings array Yes 1–100 reading objects (below)
client object No Identifies the client software. A third-party client should send kind: "custom", optionally with name — see Client identity.
Field Type Required Constraints / notes
timestamp string (ISO 8601) Yes UTC recommended
indoor_temp float Yes 30.0–120.0 °F
hvac_state string Yes "HEAT", "COOL", "ECO", "DRY", "OFF", or "FAN". Case-insensitive. Common aliases ("cooling", "heating", "eco_cool", "dehumidify", "fan_only", "idle") are normalized server-side. "auto" and "heat_cool" say what the unit may do, not what it is doing, and are recorded as "OFF". Any other value is rejected with 422. "ECO" and "DRY" are tracked separately from "COOL".
indoor_humidity float No Percent (0–100)
outdoor_temp float No Fahrenheit
target_temp float No Current thermostat setpoint
fan_mode string No Fan-speed label, e.g. "auto", "low", "high". Stored as sent.
power_watts float No Current power draw. Send only if the meter measures this specific unit — a shared/whole-home meter can mislead the model.
hvac_action string No The climate entity’s hvac_action attribute ("cooling", "heating", "idle", "off", …) — whether the unit is actively conditioning right now, as opposed to its standing mode. Send when available; improves accuracy.
commanded_hvac_mode string No The mode your integration last commanded ("COOL"/"HEAT"/"OFF"). Send when available; improves accuracy.
commanded_fan_mode string No The fan label your integration last commanded.
commanded_setpoint float No The setpoint (°F) your integration last commanded.
indoor_source string No Which source produced indoor_temp: "sensor" if you read a separate room-temperature sensor, "entity" if you used the thermostat’s own reading. Any other value is rejected with 422. Send it when you know.
override_active boolean No true if your client’s own local override, rather than the schedule, was commanding the unit when you took the reading. Leave it out if your client has no such override: omitted means “not reported”, which is recorded differently from false.
room_target float No The room temperature the schedule asked for in that slot (schedule.room_target_temps[slot], returned by GET /api/v1/schedules), as opposed to target_temp, the setpoint the unit was carrying. Send it only if your client steers the unit toward room_target_temps; otherwise leave it out.
appliance_id string (UUID) No Tags the reading with a specific HVAC (or dehumidifier) unit. A reading without it is accepted but can’t be attributed to one unit, so send it on every reading once the account has more than one reading-producing appliance. When present it must be an HVAC or dehumidifier appliance the caller owns, else 400.

The batch-level client object names the client software. It never affects the schedules you get back, and leaving the whole block out changes nothing else.

Field Type Required Constraints / notes
kind string Yes (within client) "hacs" for a self-install integration, "node" for a managed fleet node, "custom" for any third-party client. Anything else is rejected with 422.
version string Yes (within client) Your client’s version string, 1–40 characters.
node_bundle string No The node bundle tag, if you run one. null otherwise.
ha_version string No Your Home Assistant version, if you have one.
name string No A label for your client, 1–40 characters (letters, digits, space, ., _, -). Send it with kind: "custom" to say what your client is.

Building your own client? Send {"kind": "custom", "name": "<your client>"}. Before this field existed, a third-party client had no honest value for kind and was told to omit the block entirely; "custom" is now that honest value.

Terminal window
curl -X POST https://api.hungrymachines.io/api/v1/readings \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"readings": [
{
"timestamp": "2025-11-18T14:30:00+00:00",
"indoor_temp": 72.5,
"hvac_state": "COOL"
}
]
}'
{
"accepted": 1
}

The accepted count reflects how many readings were stored.

Status Detail Cause
400 "appliance_id <uuid> is not a hvac or dehumidifier appliance owned by this user" appliance_id doesn’t reference an HVAC or dehumidifier unit the caller owns
401 "Not authenticated" No Authorization header
401 "Invalid credentials" The token is not valid. An expired token gets this string on this route, so refresh it and retry.
422 Array detail (see Errors & limits) A field failed validation: indoor_temp outside 30–120, an unrecognised hvac_state, zero or more than 100 readings, a non-string label, an indoor_source other than "sensor" / "entity", a client.kind that isn’t "hacs", "node" or "custom", a client.version that is empty or over 40 characters, or a client.name that’s empty, over 40 characters, or has a character other than letters/digits/space/./_/-. The whole batch is rejected.
429 "Rate limit exceeded: max <cap> readings per user per day (scaled to <N> active reading-producing appliance(s))" Over the daily reading ceiling (see below)
503 "Database unavailable" Temporary; retry

The ceiling is 600 readings per active reading-producing appliance (HVAC or dehumidifier), per account, per UTC day. An account with no such appliance registered yet gets 600. One unit reporting every 5 minutes sends 288 a day, so the limit only trips on a stuck loop or a very large catch-up batch.

Hungry Machines uses your readings to learn how your home heats and cools and to generate the optimized schedules you pull from /api/v1/schedule. Schedules improve as readings accumulate, so keep pushing consistently; gaps slow that down, because there’s less to learn from. A stored schedule does not wait for readings: it appears with the first planning run after the appliance is registered (see Schedule).