Build With an AI Agent
Who this is for
Section titled “Who this is for”You’re a coding agent (or someone prompting one) building a client that talks to the Hungry Machines API directly — not through Home Assistant. This page is the shortest path from nothing to a working client: the exact calls, in order, and the rules that keep a client correct across accounts in different states.
What the API does
Section titled “What the API does”Hungry Machines builds optimized operating schedules for a home’s HVAC, EV charger, water heater and battery. A client registers the home’s appliances and comfort preferences, pushes sensor readings on an ongoing basis, and pulls a schedule after each overnight run and whenever it changes during the day — 48 commands, one per 30-minute slot, ready to apply as given. The optimization itself is not part of this contract; treat the schedule you receive as the answer, not as something to re-derive.
Setup, in order
Section titled “Setup, in order”Every path below hangs off https://api.hungrymachines.io, and every call after login carries Authorization: Bearer <access_token>.
Run these once per account:
-
POST /auth/signup(signUp) — create the account, sendingtimezone(an IANA zone name) so schedules key to the right local day from the start, or send the person through signup on hungrymachines.io instead, which also sets up billing and onboarding. Programmatic signup skips both, so use it for test accounts. -
POST /auth/login(logIn) — exchange email and password foraccess_tokenandrefresh_token. -
PUT /api/v1/preferences(updatePreferences) — set comfort preferences (base_temperature,optimization_mode,time_away,time_home, and so on). All fields are optional; send only what the person configured. -
POST /api/v1/appliances(createAppliance) — register each appliance: anappliance_typefromhvac,ev_charger,home_battery,water_heater,solar,dehumidifier,robot, aname, and a type-specificconfig. Every type butsolarrequiresconfig.entity_id— the entity your client reads sensors from and writes commands to. An HVAC unit is:{"appliance_type": "hvac","name": "Living Room AC","config": {"entity_id": "climate.living_room","hvac_type": "central_ac","home_size_sqft": 2000}}The response is
{"appliance_id": "<uuid>"}— keep it, readings and per-appliance routes are keyed by it. Each type’s fullconfigtable is on Appliances & Unified Schedules. -
For EV chargers, batteries and water heaters only:
POST /api/v1/appliances/{appliance_id}/constraints(setApplianceConstraints) — target charge, minimum charge, and deadline. HVAC comfort lives in step 3 and in per-appliance preferences, not here.
The daily loop
Section titled “The daily loop”Once setup is done, run four recurring tasks:
- Every ~5 minutes — push readings. HVAC appliances:
POST /api/v1/readings(pushReadings). Every other appliance type:POST /api/v1/appliances/{appliance_id}/readings(pushApplianceReadings). This is the input the optimizer learns from; the more consistently it arrives, the better the schedules get. - Once a day — push the weather forecast.
POST /api/v1/weather(pushWeather) sends the home’s local hourly forecast. Skip it and the optimizer plans against a generic fallback for that location, which is the single largest avoidable loss of accuracy in a custom client. - After the overnight run — pull the schedule.
GET /api/v1/schedules(getSchedules) returns today’s schedule for every registered appliance in one call. Apply the command for the current slot locally; re-check which slot you’re in every 30 minutes. - Every few minutes — check for a newer schedule.
GET /api/v1/schedules/updated-atreturns one timestamp. A plan for today can be replaced during the day, after a settings change and when the API re-plans the rest of the day. Keep the last value you saw and pull the schedule again when it changes.
If the person changes a preference or constraint and the UI should reflect the new schedule immediately, call POST /api/v1/schedule/recompute (recomputeSchedule) instead of waiting for the next scheduled run. It replans while you wait (usually a few seconds; allow about 20 seconds per HVAC unit) and returns date, the same appliances entries as getSchedules, and a results array. It does not return the top-level optimization_enabled, integration_health or divergence_summary.
- Apply schedules as given. Each schedule is 48 commands, one per 30-minute slot. Don’t reinterpret, smooth, or second-guess a slot’s value — apply it and move to the next.
- Slot 0 is local midnight. Convert your own clock to the home’s local time to find the current slot (
slot = hour * 2 + (1 if minute >= 30 else 0)); every timestamp the API sends or receives is UTC, but slot indexing is local. - Handle both signup response shapes.
POST /auth/signupreturns either an authenticated session (access_tokenand friends) or, when the account requires email confirmation, only a pendinguserrow with no session fields. Branch on whetheraccess_tokenis present. - Handle
source: "defaults". An appliance with no stored plan yet is flagged"defaults". OnGET /api/v1/schedulesitsscheduleis an empty object,{}: there are no setpoints and no comfort band, so send that appliance no commands. The first plan is stored by the first recompute (a settings save, orPOST /api/v1/schedule/recompute) or the next overnight run, and from then onsourceis"optimization"withsetpoint_tempspopulated. The single-unit routeGET /api/v1/schedulebehaves differently: its defaults response carries a band inhigh_temps/low_tempswithsetpoint_temps: null, and there you apply the midpoint,(high_temps[slot] + low_temps[slot]) / 2. - Check for a key before reading it. On
GET /api/v1/schedulesa schedule key is present with a value or absent, nevernull. Read it defensively (schedule.get("setpoint_temps"),schedule.setpoint_temps ?? null) — indexing it blind raises on exactly the accounts a new client is most likely to be testing against. The same holds fortemp_trajectory,hvac_mode_scheduleandfan_mode_schedule. - Gate on
optimization_enabled. Every entry carries the flag. Do not apply an entry’s schedule while it isfalse. - Send
client: {kind: "custom", name: "<your client>"}on your readings payload.POST /api/v1/readingsaccepts an optionalclientblock;kindalso takeshacsandnode— the Hungry Machines Home Assistant integration and the Home Hub — but"custom"is the honest value for your own client, andnameis a free-text label for it. The spec’s example showskind: "hacs"only because that example is the integration’s own payload.
What your client owns vs. what the API owns
Section titled “What your client owns vs. what the API owns”Your client owns local device I/O: reading sensors, writing setpoints and on/off commands to hardware, hysteresis, and any safety limits. The API owns the optimization: given your readings, preferences and constraints, it decides what the schedule should be. Don’t try to recompute or validate that decision locally — apply it.
Two optional command arrays need a rule your client has to get right, because applying them literally breaks real hardware:
fan_mode_schedule(present when fan control applies to the unit: itsoptimize_hvac_fanpreference is on and it reports distinct fan speeds, it is registered as awindow_ac, or it is a warm-up day) holds 48 values fromlow,high,auto.autoandoffare sentinels meaning “leave the unit’s fan alone” — they are not commands. Send a fan command only for a concreteloworhigh, and only when the entity’s own fan-mode vocabulary has that option. On many mini-splits and Tuya-style thermostats, switching the fan to Auto engages an Eco preset that overrides the setpoint you just applied.hvac_mode_schedule(present when mode control applies to the unit: itsoptimize_hvac_modepreference is on, it is registered as awindow_ac, or it is a warm-up day) holds per-slot modes. When it is present, apply it; the only modes the API ever commands areCOOL,HEATandOFF, and hereOFFis a real command.
Both arrays come only from GET /api/v1/schedules and the per-appliance schedule route, never from GET /api/v1/schedule.
Test it before the home has history
Section titled “Test it before the home has history”A fresh account has no readings yet, so you can verify the whole loop without waiting days for real data:
- Create an account and log in (or use an existing test account).
PUT /api/v1/preferenceswith a comfort band.POST /api/v1/appliancesto register one HVAC appliance.POST /api/v1/readingswith a handful of readings for that appliance.POST /api/v1/schedule/recompute.
Expect results to list the HVAC appliance with status: "ok", and its entry in appliances to read source: "optimization" with a 48-value setpoint_temps. savings_pct can be 0.0 on an account with only a few readings; that is a correct result, not a bug in your client. If results comes back empty, the run could not start for the account, and the entry still reads "schedule": {} with source: "defaults". Your client should handle both.
Copy-ready prompt
Section titled “Copy-ready prompt”Paste this into a coding agent to start a client from scratch:
Build a client for the Hungry Machines API.
Spec: https://docs.hungrymachines.io/openapi.json (OpenAPI 3.1 — use it togenerate request/response types, or read it directly)Full docs as plain text: https://docs.hungrymachines.io/llms-full.txt
Base URL: https://api.hungrymachines.io
Follow the contract exactly:- Create the account with POST /auth/signup, sending timezone (an IANA zone name, e.g. "America/Los_Angeles") so schedules key to the right local day from the start.- Log in with POST /auth/login, refresh with POST /auth/refresh on a 401, and send Authorization: Bearer <access_token> on every other call.- Set preferences with PUT /api/v1/preferences, then register appliances with POST /api/v1/appliances (appliance_type "hvac" plus a config whose entity_id names the local climate entity), in that order.- Push readings roughly every 5 minutes (POST /api/v1/readings for HVAC, POST /api/v1/appliances/{appliance_id}/readings for everything else). On POST /api/v1/readings, send the optional "client" block as {"kind": "custom", "name": "<your client>"}.- Push the home's hourly forecast once a day with POST /api/v1/weather.- Pull the day's schedule with GET /api/v1/schedules after the overnight run, poll GET /api/v1/schedules/updated-at every few minutes and pull again when its value changes. Apply each 30-minute slot's command as given, without reinterpreting it. Slot 0 is local midnight. Skip any entry whose optimization_enabled is false.- Handle both response shapes from POST /auth/signup (session vs. pending email confirmation).- Handle source: "defaults": on GET /api/v1/schedules the appliance's schedule is an empty object, so send it no commands. Schedule keys on that route are present or absent, never null; check before reading.- If hvac_mode_schedule is present, apply its per-slot "OFF"/"COOL"/"HEAT". If fan_mode_schedule is present, send a fan command only for "low" or "high"; "auto" and "off" there mean "do not send a fan command".
Do not make any claims about energy savings or how the schedule iscomputed — treat the schedule as an opaque result to apply.Next steps
Section titled “Next steps”- OpenAPI Description — the machine-readable spec this guide is built from
- docs llms.txt and llms-full.txt — the whole site as plain text
- Errors, Limits & Stability — error codes, rate limits, what’s safe to rely on
- Build your own client — the same material written for a person, with full worked examples
- Reference Clients — copy-ready, dependency-free Python and JavaScript files that implement this guide’s calls