# Build With an AI Agent

The page to read first if an AI coding agent — or the person directing one — is building a client against the Hungry Machines API.

## 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

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

Every path below hangs off `https://api.hungrymachines.io`, and every call after login carries `Authorization: Bearer <access_token>`.

Run these once per account:

1. **`POST /auth/signup`** (`signUp`) — create the account, sending `timezone` (an IANA zone name) so schedules key to the right local day from the start, or send the person through signup on [hungrymachines.io](https://hungrymachines.io) instead, which also sets up billing and onboarding. Programmatic signup skips both, so use it for test accounts.
2. **`POST /auth/login`** (`logIn`) — exchange email and password for `access_token` and `refresh_token`.
3. **`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.
4. **`POST /api/v1/appliances`** (`createAppliance`) — register each appliance: an `appliance_type` from `hvac`, `ev_charger`, `home_battery`, `water_heater`, `solar`, `dehumidifier`, `robot`, a `name`, and a type-specific `config`. Every type but `solar` requires `config.entity_id` — the entity your client reads sensors from and writes commands to. An HVAC unit is:

   ```json
   {
     "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 full `config` table is on [Appliances & Unified Schedules](/api-reference/appliances/).
5. 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

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-at`** returns 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`.

## Rules

- **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/signup` returns either an authenticated session (`access_token` and friends) or, when the account requires email confirmation, only a pending `user` row with no session fields. Branch on whether `access_token` is present.
- **Handle `source: "defaults"`.** An appliance with no stored plan yet is flagged `"defaults"`. On `GET /api/v1/schedules` its `schedule` is 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, or `POST /api/v1/schedule/recompute`) or the next overnight run, and from then on `source` is `"optimization"` with `setpoint_temps` populated. The single-unit route `GET /api/v1/schedule` behaves differently: its defaults response carries a band in `high_temps` / `low_temps` with `setpoint_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/schedules` a schedule key is present with a value or absent, never `null`. 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 for `temp_trajectory`, `hvac_mode_schedule` and `fan_mode_schedule`.
- **Gate on `optimization_enabled`.** Every entry carries the flag. Do not apply an entry's schedule while it is `false`.
- **Send `client: {kind: "custom", name: "<your client>"}` on your readings payload.** `POST /api/v1/readings` accepts an optional `client` block; `kind` also takes `hacs` and `node` — the Hungry Machines Home Assistant integration and the Home Hub — but `"custom"` is the honest value for your own client, and `name` is a free-text label for it. The spec's example shows `kind: "hacs"` only because that example is the integration's own payload.

## 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: its `optimize_hvac_fan` preference is on and it reports distinct fan speeds, it is registered as a `window_ac`, or it is a warm-up day) holds 48 values from `low`, `high`, `auto`. **`auto` and `off` are sentinels meaning "leave the unit's fan alone" — they are not commands.** Send a fan command only for a concrete `low` or `high`, 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: its `optimize_hvac_mode` preference is on, it is registered as a `window_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 are `COOL`, `HEAT` and `OFF`, and here `OFF` is 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

A fresh account has no readings yet, so you can verify the whole loop without waiting days for real data:

1. Create an account and log in (or use an existing test account).
2. `PUT /api/v1/preferences` with a comfort band.
3. `POST /api/v1/appliances` to register one HVAC appliance.
4. `POST /api/v1/readings` with a handful of readings for that appliance.
5. `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

Paste this into a coding agent to start a client from scratch:

```text
Build a client for the Hungry Machines API.

Spec: https://docs.hungrymachines.io/openapi.json (OpenAPI 3.1 — use it to
generate 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 is
computed — treat the schedule as an opaque result to apply.
```

## Next steps

- [OpenAPI Description](/api-reference/openapi/) — the machine-readable spec this guide is built from
- [docs llms.txt](/llms.txt) and [llms-full.txt](/llms-full.txt) — the whole site as plain text
- [Errors, Limits & Stability](/api-reference/errors-and-limits/) — error codes, rate limits, what's safe to rely on
- [Build your own client](/guides/build-your-own-client/) — the same material written for a person, with full worked examples
- [Reference Clients](/guides/reference-clients/) — copy-ready, dependency-free Python and JavaScript files that implement this guide's calls

Canonical page: https://docs.hungrymachines.io/guides/build-with-an-agent/
