Custom Home Assistant Integration
Already a Home Assistant power user? You can build your own integration on top of the Hungry Machines API — custom Lovelace cards, automations that consume the schedule, dashboards tuned to your home. The default Energy Dashboard is open source and a good starting point if you want to fork it; this guide walks you through doing it from scratch.
You’ll get the most flexibility out of this path, but you’re responsible for token refresh, sensor wiring, and applying setpoints. The default dashboard handles those for you — only take this path if you want that control.
Before you start
Section titled “Before you start”-
Sign up at hungrymachines.io/signup and confirm your email. Always sign up through the website — it sets up your billing, profile, and subscription state. Programmatic signup skips those steps.
-
Verify Home Assistant can reach the API:
Terminal window curl -sI https://api.hungrymachines.io/healthYou should see
HTTP/2 200. If your HA instance is on a corporate network or VPN, make sure outbound HTTPS toapi.hungrymachines.iois allowed.
Step 1 — Get an access token
Section titled “Step 1 — Get an access token”You’ll exchange your Hungry Machines credentials for an access token by calling the API directly. Save the result somewhere durable — you’ll wire it into Home Assistant secrets next.
curl -X POST https://api.hungrymachines.io/auth/login \ -H "Content-Type: application/json" \ -d '{"email": "you@example.com", "password": "your-password"}'Response shape:
{ "access_token": "eyJhbGciOiJFUzI1NiIs...", "refresh_token": "v1.Mfg2...", "expires_in": 3600, "expires_at": 1735689600, "token_type": "bearer", "user": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "you@example.com" }}The user.id is your Supabase user UUID; you don’t need to read it for the daily loop, but it’s useful when correlating logs.
The access token is good for 1 hour. The refresh token is good for 30 days. Both rotate on every refresh — see the Authentication reference for the full lifecycle.
Step 2 — Wire credentials into Home Assistant secrets
Section titled “Step 2 — Wire credentials into Home Assistant secrets”Add to secrets.yaml:
hm_access_token: "eyJhbGciOiJFUzI1NiIs..."hm_refresh_token: "v1.Mfg2..."Step 3 — Push readings every 5 minutes
Section titled “Step 3 — Push readings every 5 minutes”Define a rest_command in configuration.yaml that fires every 5 minutes:
rest_command: hm_push_reading: url: "https://api.hungrymachines.io/api/v1/readings" method: POST headers: Authorization: "Bearer !secret hm_access_token" Content-Type: "application/json" payload: > {"readings": [{ "timestamp": "{{ now().isoformat() }}", "indoor_temp": {{ states('sensor.indoor_temperature') }}, "hvac_state": "{{ states('climate.thermostat') | upper }}", "outdoor_temp": {{ states('sensor.outdoor_temperature') }} }]}
automation: - alias: "HM — push reading every 5 min" trigger: - platform: time_pattern minutes: "/5" action: - service: rest_command.hm_push_readingAdjust the entity IDs (sensor.indoor_temperature, climate.thermostat, sensor.outdoor_temperature) to match your setup.
See API Reference — Readings for the full request schema (humidity, target temp, fan mode, power draw all optional).
Step 4 — Pull the optimized schedule once a day
Section titled “Step 4 — Pull the optimized schedule once a day”Set up a REST sensor that pulls today’s schedule after the nightly optimization runs:
rest: - resource: "https://api.hungrymachines.io/api/v1/schedule" headers: Authorization: "Bearer !secret hm_access_token" scan_interval: 86400 # once per day sensor: - name: "HM Schedule Source" value_template: "{{ value_json.source }}" - name: "HM Savings Pct" value_template: "{{ value_json.estimated_savings_pct }}" unit_of_measurement: "%" - name: "HM Schedule Date" value_template: "{{ value_json.date }}"The full schedule object (48 half-hour intervals with high_temps and low_temps) is exposed under state_attr('sensor.hm_schedule_source', 'all_attributes') if you parse the JSON yourself, or you can split each field into a separate sensor entry.
See API Reference — Schedule for the full response shape.
Step 5 — Apply setpoints based on the current interval
Section titled “Step 5 — Apply setpoints based on the current interval”Each schedule has 48 half-hour intervals (interval = hour * 2 + (minute >= 30)). The thermostat command for each interval lives in schedule.setpoint_temps — apply that single value, not the high/low band. The high/low arrays are display-only comfort bounds for charting.
Build a template sensor that picks the current interval’s target. Fall back to the midpoint of high_temps/low_temps when setpoint_temps is null (the defaults path before the first thermal model is fit):
template: - sensor: - name: "HM Current Setpoint" unit_of_measurement: "°F" state: > {% set s = state_attr('sensor.hm_schedule_full', 'schedule') %} {% set idx = (now().hour * 2) + (1 if now().minute >= 30 else 0) %} {% if s.setpoint_temps is not none %} {{ s.setpoint_temps[idx] }} {% else %} {{ (s.high_temps[idx] + s.low_temps[idx]) / 2 }} {% endif %}Then a simple automation applies the setpoint to your thermostat whenever it changes (the 30-min interval boundary will trigger a state change):
automation: - alias: "HM — apply current setpoint" trigger: - platform: state entity_id: sensor.hm_current_setpoint action: - service: climate.set_temperature target: entity_id: climate.thermostat data: temperature: "{{ states('sensor.hm_current_setpoint') | float }}"The exact climate.set_temperature payload depends on your thermostat integration — some climate entities want temperature, others target_temp_low/target_temp_high, others target_temp_high only. Check Developer Tools → Services for your specific entity.
Step 6 — Custom Lovelace card
Section titled “Step 6 — Custom Lovelace card”Once your sensors are wired up, build a Lovelace card however you want. A minimal example:
type: vertical-stackcards: - type: markdown content: | ## Hungry Machines **Source:** {{ states('sensor.hm_schedule_source') }} **Savings:** {{ states('sensor.hm_savings_pct') }}% - type: gauge entity: sensor.hm_current_high_temp min: 60 max: 90 name: "Cool to" - type: gauge entity: sensor.hm_current_low_temp min: 60 max: 90 name: "Heat to"Apex Charts, Mini Graph Card, or any other community Lovelace plugin can render the 48-interval schedule as a series.
Daily token refresh recipe
Section titled “Daily token refresh recipe”Add an automation that refreshes the access token every 23 hours and writes it back to secrets.yaml:
rest_command: hm_refresh_token: url: "https://api.hungrymachines.io/auth/refresh" method: POST headers: Content-Type: "application/json" payload: > {"refresh_token": "!secret hm_refresh_token"}
automation: - alias: "HM — refresh access token" trigger: - platform: time_pattern hours: "/23" action: - service: rest_command.hm_refresh_token response_variable: refresh - service: shell_command.update_hm_secrets data: access_token: "{{ refresh.content.access_token }}" refresh_token: "{{ refresh.content.refresh_token }}"Implementing the shell_command.update_hm_secrets is left to you — typically a small sed script that rewrites secrets.yaml. Or move the credentials into an input_text helper instead of secrets.yaml so you can update them without filesystem writes.
What you’re sending vs receiving
Section titled “What you’re sending vs receiving”| Direction | Endpoint | Frequency | Purpose |
|---|---|---|---|
| Push | POST /api/v1/readings | Every 5 min | Sensor data → optimizer trains thermal model |
| Push | POST /api/v1/appliances/{id}/readings | Every 5 min (per appliance) | EV / battery / water heater state |
| Pull | GET /api/v1/schedule | Daily after 05:00 UTC | HVAC setpoints for next 24h |
| Pull | GET /api/v1/schedules | Daily after 05:00 UTC | All-appliance schedules in one call |
| CRUD | POST/GET/PUT /api/v1/appliances | One-time | Register devices, set constraints |
| CRUD | GET/PUT /api/v1/preferences | As-needed | Comfort settings |
Full per-endpoint schemas are in the API Reference.
Troubleshooting
Section titled “Troubleshooting”Readings rejected with 401
Section titled “Readings rejected with 401”Your access token expired. Refresh it and retry — see Step 1 of Authentication.
Schedule sensor returns empty
Section titled “Schedule sensor returns empty”The optimizer needs ~3 days of continuous readings before the fitter builds your first per-user thermal model. Until then, source: "defaults" returns a flat comfort band based on your preferences with setpoint_temps: null. Keep pushing readings — the daily 06:30 UTC initial-fit job picks new users up automatically once the data threshold is met.
Setpoints aren’t being applied to the thermostat
Section titled “Setpoints aren’t being applied to the thermostat”Check that your climate.set_temperature service call uses the right keys for your thermostat. The example in Step 5 sends a single temperature value (which matches what most modern integrations want, and what the optimizer’s setpoint_temps is designed for); some older integrations expect target_temp_high/target_temp_low instead. Test the call manually from Developer Tools → Services first.
Next steps
Section titled “Next steps”- Authentication — token lifecycle reference
- API Reference — Schedules — full schedule response shape
- API Reference — Appliances — registering EV chargers, batteries, water heaters
- Build Your Own Client — same API, non-HA platforms