Appliances & Unified Schedules
The appliance endpoints let you register devices (HVAC, EV charger, home battery, water heater, solar), push per-device sensor data, set optimization constraints, and pull schedules.
POST /api/v1/appliances
Section titled “POST /api/v1/appliances”Register a new appliance.
Auth: Required
curl -X POST https://api.hungrymachines.io/api/v1/appliances \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "appliance_type": "ev_charger", "name": "Tesla Model 3", "config": { "battery_capacity_kwh": 75, "max_charge_rate_kw": 7.2, "efficiency": 0.9, "entity_id": "switch.tesla_charger", "soc_entity_id": "sensor.tesla_battery_level" } }'Response (201):
{ "appliance_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}Config shapes by type
Section titled “Config shapes by type”Server-side validation is strict — out-of-range or missing required fields return 400 with the offending field name in the detail message.
{ "hvac_type": "central_ac", "home_size_sqft": 2000, "entity_id": "climate.living_room"}| Field | Type | Constraints |
|---|---|---|
hvac_type | string | "central_ac", "window_ac", "heat_pump", or "furnace" |
home_size_sqft | integer | 100 – 20 000 |
entity_id | string | Required. The climate.* entity that exposes current_temperature and accepts set_temperature calls. Used both for sensor input to the thermal model and for applying optimized setpoints. |
EV Charger
Section titled “EV Charger”{ "battery_capacity_kwh": 75, "max_charge_rate_kw": 7.2, "efficiency": 0.9, "entity_id": "switch.tesla_charger", "soc_entity_id": "sensor.tesla_battery_level"}| Field | Type | Constraints |
|---|---|---|
battery_capacity_kwh | float | 0 – 300 |
max_charge_rate_kw | float | 0 – 50 |
efficiency | float | 0.5 – 1.0 (charging efficiency factor) |
entity_id | string | Required. The switch.* (or equivalent) entity the integration toggles to start/stop charging. |
soc_entity_id | string | null | Optional. A sensor.* exposing state-of-charge as 0–100. When set, the readings poller pushes SoC every 5 min so the optimizer’s required-energy calculation has live data. |
Home Battery
Section titled “Home Battery”{ "capacity_kwh": 13.5, "max_charge_rate_kw": 5.0, "max_discharge_rate_kw": 5.0, "entity_id": "switch.powerwall_charge_mode", "soc_entity_id": "sensor.powerwall_charge"}| Field | Type | Constraints |
|---|---|---|
capacity_kwh | float | 0 – 300 |
max_charge_rate_kw | float | 0 – 50 |
max_discharge_rate_kw | float | 0 – 50 |
entity_id | string | Required. The switch / button / select entity that toggles charge mode. |
soc_entity_id | string | null | Optional. sensor.* for battery SoC %. Same shape as EV. |
Water Heater
Section titled “Water Heater”{ "tank_size_gallons": 50, "element_watts": 4500, "insulation_factor": 0.03, "entity_id": "switch.water_heater", "temp_entity_id": "sensor.water_heater_temperature"}| Field | Type | Constraints |
|---|---|---|
tank_size_gallons | integer | 0 – 200 |
element_watts | integer | 0 – 10 000 |
insulation_factor | float | 0.01 – 0.05 (heat-loss rate) |
entity_id | string | Required. The switch.* (resistive elements) or climate.* (smart tanks) entity the integration toggles each 30-min boundary. |
temp_entity_id | string | null | Optional. sensor.* exposing tank temperature in °F. When set, the readings poller pushes tank temp every 5 min. |
Solar is forecast-only — no entity_id, no readings to push. The nightly job uses the system size + orientation to build a 48-element generation curve that discounts effective grid pricing for every other appliance during daylight intervals.
{ "system_size_kw": 6.4, "azimuth_degrees": 180, "tilt_degrees": 20}| Field | Type | Constraints | Notes |
|---|---|---|---|
system_size_kw | float | 0 – 100 | DC nameplate capacity. |
azimuth_degrees | integer | 0 – 360 | Panel azimuth — 180 (south) is the default for the northern hemisphere. |
tilt_degrees | integer | 0 – 90 | Panel tilt from horizontal. |
Errors
Section titled “Errors”| Status | Detail | Cause |
|---|---|---|
| 400 | "appliance_type must be one of ('hvac', 'ev_charger', 'home_battery', 'water_heater', 'solar')" | Unknown type |
| 400 | "<field>: <message>" | Per-type config validation failure (e.g. "entity_id: String should have at least 3 characters", "home_size_sqft: Input should be less than or equal to 20000") |
| 403 | "Upgrade required" | Free-tier limit (1 HVAC only) |
GET /api/v1/appliances
Section titled “GET /api/v1/appliances”List all registered appliances.
Auth: Required
curl https://api.hungrymachines.io/api/v1/appliances \ -H "Authorization: Bearer YOUR_TOKEN"Response (200):
[ { "id": "a1b2c3d4-...", "user_id": "550e8400-...", "appliance_type": "ev_charger", "name": "Tesla Model 3", "config": { "battery_capacity_kwh": 75, "max_charge_rate_kw": 7.2, "efficiency": 0.9, "entity_id": "switch.tesla_charger", "soc_entity_id": "sensor.tesla_battery_level" }, "is_active": true, "created_at": "2025-11-15T10:30:00+00:00" }]PUT /api/v1/appliances/{appliance_id}
Section titled “PUT /api/v1/appliances/”Update an appliance’s name or config.
Auth: Required
curl -X PUT https://api.hungrymachines.io/api/v1/appliances/a1b2c3d4-... \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Tesla Model 3 (Garage)", "config": { "battery_capacity_kwh": 75, "max_charge_rate_kw": 11.5, "efficiency": 0.9, "entity_id": "switch.tesla_charger" } }'When updating config, send the full per-type object — partial config patches are not supported because the server re-validates the whole shape.
Response (200): Full appliance object (same shape as GET list item).
POST /api/v1/appliances/{appliance_id}/readings
Section titled “POST /api/v1/appliances//readings”Push sensor data for a specific appliance.
Auth: Required
curl -X POST https://api.hungrymachines.io/api/v1/appliances/a1b2c3d4-.../readings \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "readings": [ { "timestamp": "2025-11-18T14:30:00+00:00", "state": "CHARGING", "value": 45.2, "power_watts": 7200.0, "metadata": { "voltage": 240, "current": 30 } } ] }'Fields
Section titled “Fields”| Field | Type | Required | Description |
|---|---|---|---|
timestamp | string (ISO 8601) | Yes | |
state | string | Yes | Free-form label, typically "CHARGING", "IDLE", "ON", "OFF", "DISCHARGING". Not server-validated. |
value | float | Yes | Charge % (0–100) for EV/battery; tank temperature (60–180 °F) for water heater. Server-validated per appliance type. |
power_watts | float | No | Current power draw |
metadata | object | No | Any additional data, stored verbatim |
Response (201):
{ "accepted": 1}Rate limit: 300 readings per user per day (shared across /api/v1/readings and every appliance’s /readings).
Errors
Section titled “Errors”| Status | Detail | Cause |
|---|---|---|
| 400 | "Value must be 0-100 (percent) for ev_charger" | EV/battery value out of range |
| 400 | "Value must be 60-180 (degrees F) for water_heater" | Water heater value out of range |
| 404 | "Appliance not found" | Bad appliance_id, or not owned by the caller |
| 429 | "Rate limit exceeded: max 300 readings per user per day" | Combined daily count exceeded |
POST /api/v1/appliances/{appliance_id}/constraints
Section titled “POST /api/v1/appliances//constraints”Set optimization constraints for a specific appliance. The optimizer uses these on its next run. Constraints are stored verbatim in the appliance’s constraints JSONB column — only the optimizer’s per-type code interprets them, so there’s no strict server-side schema.
Auth: Required
EV Charger constraints
Section titled “EV Charger constraints”curl -X POST https://api.hungrymachines.io/api/v1/appliances/a1b2c3d4-.../constraints \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "target_charge_pct": 80, "min_charge_pct": 30, "deadline_time": "07:00", "current_charge_pct": 35 }'Home Battery constraints
Section titled “Home Battery constraints”{ "target_charge_pct": 90, "min_charge_pct": 20, "deadline_time": "23:59"}Water Heater constraints
Section titled “Water Heater constraints”{ "max_temp_f": 140, "min_temp_f": 110}Response (200):
{ "status": "ok", "constraints": { "..." }}GET /api/v1/appliances/{appliance_id}/schedule
Section titled “GET /api/v1/appliances//schedule”Get the optimized schedule for a single appliance.
Auth: Required
curl https://api.hungrymachines.io/api/v1/appliances/a1b2c3d4-.../schedule \ -H "Authorization: Bearer YOUR_TOKEN"Per-type schedule shapes
Section titled “Per-type schedule shapes”All schedules use 48 × 30-minute intervals (24 hours).
{ "appliance_id": "...", "appliance_type": "hvac", "name": "Living Room AC", "date": "2025-11-18", "schedule": { "intervals": [0, 1, 2, "...", 47], "high_temps": [74.0, 74.0, "...(48 values)"], "low_temps": [70.0, 70.0, "...(48 values)"], "setpoint_temps": [72.0, 71.5, "...(48 values)"], "temp_trajectory": [72.1, 71.6, "...(48 values)"] }, "savings_pct": 18.5, "source": "optimization", "entities": { "entity_id": "climate.living_room" }}Apply setpoint_temps[idx] to the climate entity at each 30-min boundary, regardless of HVAC mode. high_temps and low_temps are the display-only comfort band used for charting; the thermostat does not see them. Both setpoint_temps and temp_trajectory are null on defaults rows — fall back to (high_temps[idx] + low_temps[idx]) / 2.
EV Charger
Section titled “EV Charger”{ "appliance_id": "...", "appliance_type": "ev_charger", "name": "Tesla Model 3", "date": "2025-11-18", "schedule": { "intervals": [false, false, "...", true, true, "...", false], "value_trajectory": [35.0, 35.0, "...", 57.5, 80.0, "...", 80.0], "unit": "percent" }, "savings_pct": 32.1, "source": "optimization", "entities": { "entity_id": "switch.tesla_charger", "soc_entity_id": "sensor.tesla_battery_level" }}intervals: boolean array — true = charger on, false = charger off.
value_trajectory: predicted charge level (0–100 %) at each interval.
Home Battery
Section titled “Home Battery”Same shape as EV charger: intervals (boolean), value_trajectory (percent), unit: "percent", plus an entities block.
Water Heater
Section titled “Water Heater”{ "appliance_id": "...", "appliance_type": "water_heater", "name": "Basement Water Heater", "date": "2025-11-18", "schedule": { "intervals": [true, true, false, "...", false, true, "..."], "temp_trajectory": [120.0, 128.5, 135.2, "...", 118.3, 126.7, "..."], "unit": "fahrenheit" }, "savings_pct": 22.0, "source": "optimization", "entities": { "entity_id": "switch.water_heater", "temp_entity_id": "sensor.water_heater_temperature" }}intervals: boolean array — true = element on, false = element off.
temp_trajectory: predicted tank temperature at each interval.
The entities block
Section titled “The entities block”Every per-appliance schedule entry carries an entities sub-object echoing the HA entity ids the integration needs to apply the schedule. Only fields the appliance config actually set are included — entity_id for everything except solar, plus soc_entity_id (EV / battery) or temp_entity_id (water heater) when set. Solar appliances have no entities block.
GET /api/v1/schedules
Section titled “GET /api/v1/schedules”Unified view of all appliance schedules in a single response.
Auth: Required
curl https://api.hungrymachines.io/api/v1/schedules \ -H "Authorization: Bearer YOUR_TOKEN"Response (200):
{ "date": "2025-11-18", "appliances": [ { "appliance_id": "...", "appliance_type": "hvac", "name": "Living Room AC", "schedule": { "intervals": [0, 1, "...", 47], "high_temps": [74.0, "..."], "low_temps": [70.0, "..."], "setpoint_temps": [72.0, "..."], "temp_trajectory": [72.1, "..."] }, "savings_pct": 18.5, "source": "optimization", "entities": { "entity_id": "climate.living_room" } }, { "appliance_id": "...", "appliance_type": "ev_charger", "name": "Tesla Model 3", "schedule": { "intervals": [false, "...", true, "...", false], "value_trajectory": [30.0, "...", 80.0], "unit": "percent" }, "savings_pct": 32.1, "source": "optimization", "entities": { "entity_id": "switch.tesla_charger" } } ]}Each entry in the appliances array uses the per-type schedule shape described above. Use this endpoint when you need the full picture for a multi-device home.
To force a synchronous recompute (e.g. immediately after the user saves a new constraint) and return the same shape, use POST /api/v1/schedule/recompute.
Per-type schedule summary
Section titled “Per-type schedule summary”| Type | Schedule fields | Unit |
|---|---|---|
hvac | intervals (int[48]), high_temps (float[48]), low_temps (float[48]), setpoint_temps (float[48] | null), temp_trajectory (float[48] | null) | Fahrenheit |
ev_charger | intervals (bool[48]), value_trajectory (float[48]), unit | percent (0–100) |
home_battery | intervals (bool[48]), value_trajectory (float[48]), unit | percent (0–100) |
water_heater | intervals (bool[48]), temp_trajectory (float[48]), unit | fahrenheit |
solar | No schedule — solar contributes to other appliances’ pricing curve but emits no per-interval commands. | — |