Schedule
The schedule endpoint returns today’s optimized temperature plan for an HVAC unit. Call it once daily to get 48 half-hour intervals covering the next 24 hours.
For multi-appliance schedules (HVAC + EV charger + battery + water heater in one call) use /api/v1/schedules.
GET /api/v1/schedule
Section titled “GET /api/v1/schedule”Auth: Required (Bearer JWT)
Query parameters
Section titled “Query parameters”| Param | Required | Notes |
|---|---|---|
appliance_id | Conditional | UUID of the HVAC unit whose schedule to return. Required (returns 400) when the account has 2+ HVAC units — otherwise the endpoint can’t tell which one you mean. Single-HVAC accounts may omit it; it resolves to their only HVAC. |
Example
Section titled “Example”curl "https://api.hungrymachines.io/api/v1/schedule?appliance_id=a1b2c3d4-..." \ -H "Authorization: Bearer YOUR_TOKEN"Response (200) — optimized schedule
Section titled “Response (200) — optimized schedule”{ "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)"] }, "mode": "cool", "estimated_savings_pct": 18.5, "model_confidence": 0.23, "generated_at": "2025-11-18T04:15:00+00:00", "source": "optimization"}Response fields
Section titled “Response fields”| Field | Type | Description |
|---|---|---|
date | string | Schedule date (YYYY-MM-DD) |
schedule.intervals | int[48] | Always [0, 1, ..., 47] — 48 half-hour slots |
schedule.high_temps | float[48] | Per-interval upper comfort bound (°F). Display-only — the thermostat never sees this value. |
schedule.low_temps | float[48] | Per-interval lower comfort bound (°F). Display-only. |
schedule.setpoint_temps | float[48] | null | The 48-element command to apply to the thermostat at each 30-min boundary, regardless of HVAC mode. null on source: "defaults" and on legacy rows — fall back to (high_temps[i] + low_temps[i]) / 2. |
schedule.temp_trajectory | float[48] | null | Predicted indoor-temperature line for charting. Display-only. null on defaults / legacy rows. |
mode | string | "cool", "heat", "auto", or "off" |
estimated_savings_pct | float | Display-only estimated % savings vs. a naive fixed-setpoint thermostat |
model_confidence | float | null | Display-only learning-confidence indicator in [0, 1] — higher means the schedule is based on more-established learning. null while on defaults. Treat it as a progress signal; don’t interpret the exact value. |
generated_at | string | When the schedule was produced (UTC ISO 8601) |
stale | boolean | Only present (and true) when returning a prior day’s schedule because none was produced today |
source | string | "optimization" or "defaults" |
The 48-interval shape
Section titled “The 48-interval shape”Every schedule contains exactly 48 intervals, each a 30-minute window:
| Interval | Time |
|---|---|
| 0 | 00:00–00:30 |
| 1 | 00:30–01:00 |
| … | … |
| 16 | 08:00–08:30 |
| 34 | 17:00–17:30 |
| 47 | 23:30–00:00 |
Slot 0 begins at local midnight (the account’s configured timezone). Compute the current interval as interval = hour * 2 + (1 if minute >= 30 else 0) using local time.
Response variants
Section titled “Response variants”Stale schedule
Section titled “Stale schedule”If no schedule was produced today, the most recent available one is returned with stale: true:
{ "date": "2025-11-17", "stale": true, "source": "optimization", "...": "..." }Defaults (warm-up period)
Section titled “Defaults (warm-up period)”Before Hungry Machines has learned enough about the home, the response uses the comfort band derived from your preferences with setpoint_temps: null:
{ "date": "2025-11-18", "schedule": { "intervals": [0, 1, "...", 47], "high_temps": [73.5, 73.5, "...(all same)"], "low_temps": [70.5, 70.5, "...(all same)"] }, "mode": "cool", "estimated_savings_pct": 0, "model_confidence": null, "generated_at": "2025-11-18T12:00:00+00:00", "source": "defaults"}Integration pattern
Section titled “Integration pattern”- Call
GET /api/v1/schedulewhen your client starts and once a day (after the overnight run). - Cache the response locally.
- Every 30 minutes, look up the current interval
idxand applysetpoint_temps[idx]. Ifsetpoint_tempsisnull, apply the midpoint ofhigh_temps[idx]andlow_temps[idx].
Errors
Section titled “Errors”| Status | Detail | Cause |
|---|---|---|
| 400 | "User has multiple HVAC appliances; pass appliance_id to disambiguate." | 2+ HVAC units and no appliance_id supplied |
| 401 | "Not authenticated" | Missing or invalid token |
POST /api/v1/schedule/recompute
Section titled “POST /api/v1/schedule/recompute”Recompute schedules synchronously for the account and return the fresh result. Built for “user just hit Save on a constraint editor — show the new chart now” flows. The response is the same shape as GET /api/v1/schedules (all appliances at once), plus a results array reporting each appliance’s outcome for this run.
Auth: Required (Bearer JWT)
Example
Section titled “Example”curl -X POST https://api.hungrymachines.io/api/v1/schedule/recompute \ -H "Authorization: Bearer YOUR_TOKEN"Response (200)
Section titled “Response (200)”Same shape as GET /api/v1/schedules, plus results:
{ "date": "2025-11-18", "appliances": ["<same entries as GET /api/v1/schedules>"], "results": [ { "appliance_id": "uuid-1", "appliance_type": "hvac", "name": "Office AC", "status": "ok" }, { "appliance_id": "uuid-2", "appliance_type": "hvac", "name": "Front AC", "status": "failed", "detail": "weather fetch failed" } ]}results[].status values:
| Value | Meaning |
|---|---|
ok | A fresh schedule was produced for this appliance. |
failed | This appliance did not re-optimize; detail carries a short reason. Its previous schedule is still in appliances. Tell the user it didn’t update. |
calibrating | A one-time warm-up is active for this HVAC; constraint changes won’t reflect on its chart until it completes. |
forecast | Solar — nothing to schedule. |
observe | Dehumidifier — data-collection only. |
skipped | Unknown appliance type. |
Account-level early exits (no profile, weather fetch failed, optimization_mode == "off") return an empty results array with the previous appliances entries.
Latency
Section titled “Latency”Typically 1–5 seconds. Show an “Optimizing…” overlay while the request is in flight.
When to use it
Section titled “When to use it”- Right after the user changes constraints, comfort bands, or
optimization_modeand expects to see the impact immediately. - After saving a custom rate curve via
PUT /api/v1/rates.
For the regular daily fetch, use GET /api/v1/schedule or GET /api/v1/schedules — they read the most recent stored row without triggering a recompute.
Errors
Section titled “Errors”| Status | Detail | Cause |
|---|---|---|
| 401 | "Not authenticated" | Missing or invalid token |
| 503 | "Database unavailable" | Database not reachable |