Preferences
The preferences endpoints let you configure your comfort settings and optimization mode. These values control how aggressively the optimizer shifts energy usage.
These are account-level preferences. For a home with more than one HVAC, each unit also has its own per-HVAC preferences with the same shape. Planning for a unit follows these account-level values until that unit’s own preferences are saved, and the account-level values stay authoritative for account-scope fields.
See also Rates for the electricity rate curve the optimizer saves against.
GET /api/v1/preferences
Section titled “GET /api/v1/preferences”Auth: Required (Bearer JWT)
Example
Section titled “Example”curl https://api.hungrymachines.io/api/v1/preferences \ -H "Authorization: Bearer YOUR_TOKEN"Response (200)
Section titled “Response (200)”{ "base_temperature": 72.0, "savings_level": 1, "time_away": "08:00", "time_home": "17:00", "optimization_mode": "cool", "hourly_high_temps_f": null, "hourly_low_temps_f": null, "optimize_hvac_fan": false, "optimize_hvac_mode": false, "optimization_enabled": true, "newsletter_opt_in": true, "spike_guard_enabled": true, "max_concurrent_appliances": null, "power_cap_kw": null, "base_load_reserve_kw": 0.0, "demand_charge_usd_per_kw": null, "demand_window_start": null, "demand_window_end": null}If the user hasn’t set preferences yet, defaults are returned.
Fields
Section titled “Fields”| Field | Type | Default | Description |
|---|---|---|---|
base_temperature |
float | 72.0 | Desired temperature (°F) |
savings_level |
integer | 1 | 1 = tight, 2 = moderate, 3 = aggressive. Stored and returned; it does not change the default comfort band. To widen or narrow comfort, set the two hourly arrays. |
time_away |
string | "08:00" |
When you typically leave home (HH:MM, 24-hour). Stored and returned; it does not change the default comfort band. |
time_home |
string | "17:00" |
When you typically return home (HH:MM, 24-hour). Stored and returned; it does not change the default comfort band. |
optimization_mode |
string | "cool" |
"cool", "heat", "auto", or "off" |
hourly_high_temps_f |
array[float] | null | null |
Optional 24-element per-hour upper comfort bound (°F). When set, the two hourly arrays are the comfort band. When null, the default band applies. |
hourly_low_temps_f |
array[float] | null | null |
Optional 24-element per-hour lower comfort bound (°F). Must be set together with hourly_high_temps_f. |
optimize_hvac_fan |
boolean | false |
Opt-in: also emit a per-slot fan-speed schedule. |
optimize_hvac_mode |
boolean | false |
Opt-in: also emit a per-slot HVAC-mode schedule. |
optimization_enabled |
boolean | true |
Master pause switch. When false, clients must stop applying schedules entirely until re-enabled. Schedules are still computed, so re-enabling takes effect at the next half-hour boundary. |
newsletter_opt_in |
boolean | true |
Newsletter membership. Changing only this field does not trigger a schedule recompute. |
spike_guard_enabled |
boolean | true |
Automatically avoid real-time price spikes on plans that support it. Your comfort range always applies. Turn off any time. |
max_concurrent_appliances |
integer | null | null |
Limit how many appliances (EV charger, home battery, water heater, etc.) may run at the same time. null = no limit. |
power_cap_kw |
float | null | null |
A whole-home power ceiling (kW) the planner respects. null = no cap. |
base_load_reserve_kw |
float | 0.0 |
Power (kW) always reserved for everyday always-on draw before the planner spends the rest of power_cap_kw. |
demand_charge_usd_per_kw |
float | null | null |
Your utility’s demand charge rate ($/kW), if any, so the planner avoids a new monthly peak. null = no demand charge. |
demand_window_start |
string | null | null |
HH:MM start of the hours your demand charge applies. null with demand_window_end also null = all day. |
demand_window_end |
string | null | null |
HH:MM end of the hours your demand charge applies. |
The default comfort band
Section titled “The default comfort band”Without an explicit override, every home gets the same default band, set only by base_temperature and optimization_mode. At a base of 72 °F in cool mode it is 70–73 °F for hours 0–7 and 22–23, and 67–78 °F for hours 8–21. A different base shifts both bounds by the same amount, and heat mode mirrors the shape so the wider side is below the base. savings_level, time_away and time_home do not change it. See default bands below to preview the exact per-hour values.
Hourly comfort bands (advanced)
Section titled “Hourly comfort bands (advanced)”If you want different per-hour bounds — for example, a tighter band during occupied hours and a wider band overnight — submit hourly_high_temps_f and hourly_low_temps_f together.
- Both arrays must be set together, or both must be
nullto clear the override and return to the default band. Always send both in the same request. - Each array is exactly 24 values, indexed
0(midnight) through23(11 PM local time). - Each value must be in
[50, 90]°F. - For every hour
i,hourly_low_temps_f[i] < hourly_high_temps_f[i]. - When the override is set, the explicit bounds apply at every hour.
Example: tighter [68, 74] band from 8 AM through 9 PM, wider [64, 78] band overnight.
{ "hourly_high_temps_f": [78, 78, 78, 78, 78, 78, 78, 78, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 78, 78], "hourly_low_temps_f": [64, 64, 64, 64, 64, 64, 64, 64, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 64, 64]}PUT /api/v1/preferences
Section titled “PUT /api/v1/preferences”Auth: Required (Bearer JWT)
Update one or more preference fields. All fields are optional — only the fields you include are changed; others keep their current values. To leave a field unchanged, omit it; send null only for the fields the table below marks as clearable. Any change other than newsletter_opt_in alone triggers a debounced recompute of today’s schedule.
Example
Section titled “Example”curl -X PUT https://api.hungrymachines.io/api/v1/preferences \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "base_temperature": 73.0, "savings_level": 2, "optimization_mode": "auto" }'Request body
Section titled “Request body”All fields are optional — include only what you want to change.
{ "base_temperature": 73.0, "savings_level": 2, "time_away": "07:30", "time_home": "18:00", "optimization_mode": "auto"}Validation
Section titled “Validation”| Field | Constraints |
|---|---|
base_temperature |
60.0–85.0 °F |
savings_level |
1, 2, or 3 |
time_away |
HH:MM, 24-hour, 00:00–23:59 |
time_home |
HH:MM, 24-hour, 00:00–23:59 |
optimization_mode |
"cool", "heat", "auto", "off" |
hourly_high_temps_f |
Exactly 24 finite floats, each in [50, 90] °F |
hourly_low_temps_f |
Exactly 24 finite floats, each in [50, 90] °F |
hourly_low_temps_f[i] < hourly_high_temps_f[i] |
Per-hour lower bound must be strictly less than upper bound |
hourly_high_temps_f + hourly_low_temps_f |
Both fields must be set together (or both null to clear) |
optimize_hvac_fan |
true / false |
optimize_hvac_mode |
true / false |
optimization_enabled |
true / false |
newsletter_opt_in |
true / false |
spike_guard_enabled |
true / false |
max_concurrent_appliances |
Integer >= 1, or null to clear |
power_cap_kw |
Float > 0, or null to clear |
base_load_reserve_kw |
Float >= 0 |
demand_charge_usd_per_kw |
Float >= 0, or null to clear |
demand_window_start |
HH:MM, 24-hour, 00:00–23:59, or null |
demand_window_end |
HH:MM, 24-hour, 00:00–23:59, or null |
Response (200)
Section titled “Response (200)”Returns the full preferences object after the update — every field shown under GET, not only the ones you sent. Abbreviated here:
{ "base_temperature": 73.0, "savings_level": 2, "time_away": "07:30", "time_home": "18:00", "optimization_mode": "auto", "...": "the remaining GET fields"}Errors
Section titled “Errors”| Status | Detail | Cause |
|---|---|---|
| 400 | "hourly_high_temps_f and hourly_low_temps_f must be set together or both cleared" |
Only one of the two arrays was provided as a non-null value |
| 400 | "hourly_high_temps_f and hourly_low_temps_f must each have exactly 24 values" |
Wrong array length |
| 400 | "hourly bands at hour <i> must be between 50 and 90 F" |
Value out of range at hour <i> |
| 400 | "hourly bands at hour <i> must be finite numbers between 50 and 90 F" |
Non-finite value at hour <i> |
| 400 | "hourly low must be less than high at hour <i>" |
hourly_low_temps_f[i] >= hourly_high_temps_f[i] |
| 401 | "Not authenticated" |
Missing or invalid token |
| 422 | array detail |
Any other field out of range or of the wrong type. The entry’s msg names the rule, for example "Value error, base_temperature must be between 60 and 85F". See Errors, Limits & Stability. |
| 503 | "Database unavailable" |
Database not reachable |
GET /api/v1/preferences/default-bands
Section titled “GET /api/v1/preferences/default-bands”Preview the default hourly comfort band — the band that applies when no hourly arrays are saved — without saving anything. Use this to build a “Reset to defaults” control that shows what will actually apply, or to render the default band before a user has customized it. The result depends only on base_temperature and optimization_mode.
Auth: Required (Bearer JWT)
Example
Section titled “Example”curl "https://api.hungrymachines.io/api/v1/preferences/default-bands?base_temperature=72&optimization_mode=cool" \ -H "Authorization: Bearer YOUR_TOKEN"Every query param is optional — an omitted one falls back to your stored /api/v1/preferences value.
| Param | Type | Validation | Falls back to |
|---|---|---|---|
base_temperature |
float | 60.0–85.0 °F | your stored base_temperature |
optimization_mode |
string | "cool", "heat", or "auto" |
your stored optimization_mode |
savings_level |
integer | 1, 2, or 3 | Accepted for compatibility; does not change the result |
time_away |
string | HH:MM | Accepted for compatibility; does not change the result |
time_home |
string | HH:MM | Accepted for compatibility; does not change the result |
optimization_mode="auto" resolves to the cool-shaped band for this preview. "off" is not accepted here.
Response (200)
Section titled “Response (200)”{ "hourly_high_temps_f": [73.0, 73.0, "...(24 values)"], "hourly_low_temps_f": [70.0, 70.0, "...(24 values)"], "peak_hours": [13, 21], "precool_hours": [9, 13]}hourly_high_temps_f/hourly_low_temps_f are 24-element arrays, hour 0 = midnight local time — the same shape you’d submit as an hourly override above. peak_hours/precool_hours are fixed [start, end) reference windows in local hours, returned so you can shade the usual expensive hours and the hours before them in a UI without hardcoding them. They do not vary with the query params.
Errors
Section titled “Errors”| Status | Detail | Cause |
|---|---|---|
| 400 | "base_temperature must be between 60 and 85F" |
base_temperature out of range |
| 400 | "savings_level must be 1, 2, or 3" |
savings_level out of range |
| 400 | "Time must be in HH:MM format" |
time_away or time_home malformed |
| 400 | "optimization_mode must be cool, heat, or auto" |
Unknown mode, or "off" |
| 401 | "Not authenticated" |
Missing or invalid token |
| 422 | array detail |
A query value of the wrong type, for example a non-numeric base_temperature |