Troubleshooting
401 with a fresh token
Section titled “401 with a fresh token”Symptom: You just signed in, but API calls return 401 Token expired or 401 Invalid token.
Causes:
- Clock skew. JWT validation checks
exp(expiration) against the server’s clock. If your device’s clock is more than a few minutes off, tokens may appear expired. Sync your system clock with NTP. - Token copied incorrectly. Make sure you’re using the full
access_tokenstring with no trailing whitespace or line breaks. - Wrong header format. The header must be exactly
Authorization: Bearer <access_token>— note the space afterBearerand no quotes around the token value. - Using the wrong token.
access_tokengoes in theAuthorizationheader.refresh_tokenis only for exchanging atPOST /auth/refresh.
Fix: Verify your system clock is accurate (date -u should match UTC within a few seconds). If the clock is fine, refresh your access token (see Authentication) and confirm the Authorization header is formatted correctly.
Access token expired
Section titled “Access token expired”Symptom: API calls were working, now they return 401 Token expired.
Cause: Access tokens last 1 hour. Your client needs to exchange the refresh token for a new access token before the current one expires.
Fix: Exchange your refresh token for a new access token:
curl -X POST https://api.hungrymachines.io/auth/refresh \ -H "Content-Type: application/json" \ -d '{"refresh_token": "YOUR_REFRESH_TOKEN"}'The response contains a new access_token and a new refresh_token — save both, then retry your original request with the new access token. If this call itself returns 400 Invalid Refresh Token, the refresh token is expired (30-day max) — the user needs to log in again.
Readings accepted but no schedule
Section titled “Readings accepted but no schedule”Symptom: POST /api/v1/readings returns 201 accepted, but the schedule still reads source: "defaults" with zero savings.
Cause: source is "defaults" until the first plan is stored for the appliance. Pushing readings does not store one. A plan is stored by a planning run: the first recompute (a settings save, or POST /api/v1/schedule/recompute) or the next overnight run.
What to expect:
- Before the first plan:
GET /api/v1/schedulereturnssource: "defaults", a comfort band built from your preferences, andsetpoint_temps: null.GET /api/v1/schedulesreturns that appliance with"schedule": {}andsource: "defaults". - From the first plan on:
sourceis"optimization"andsetpoint_tempsis a 48-element array, every day. Savings can read0.0on early plans, for example before any readings have arrived. - Ongoing: schedules improve as Hungry Machines sees more readings, more weather, and more of how your home responds.
Fix: Call POST /api/v1/schedule/recompute and read its results array. An entry with status: "ok" means a plan was stored. An empty results means the run could not start for the account: check that an appliance is registered and that the account’s optimization_mode is not "off". While source is "defaults" on GET /api/v1/schedule, apply the band midpoint as your setpoint: (high_temps[idx] + low_temps[idx]) / 2. On GET /api/v1/schedules there is no band to fall back to, so send that appliance no commands.
Schedule looks like flat setpoints
Section titled “Schedule looks like flat setpoints”Symptom: source is "optimization" but the high_temps and low_temps arrays have little variation — they look almost flat.
Causes:
- A narrow comfort band. The optimizer can only shift load inside the band you give it. The default band depends only on your base temperature and mode: when cooling it runs from 2 °F below base to 1 °F above overnight, and from 5 °F below to 6 °F above between 08:00 and 21:59; heating mirrors it (1 °F below to 2 °F above overnight, 6 °F below to 5 °F above by day).
savings_leveldoes not change it. If you have saved hourly bands that are tighter than that, widen them viaPUT /api/v1/preferences. - Flat rate plan. If your utility charges the same rate all day, there’s no price signal to optimize against. The optimizer works best with time-of-use (TOU) or real-time pricing.
- Mild weather. When outdoor and indoor temps are close, the HVAC runs very little and there’s less to optimize.
Fix: Check your hourly comfort bands and optimization_mode in preferences. If you’re on a flat-rate plan, the optimizer may not produce significant schedule variation — that’s expected behavior, not a bug. You can inspect (and override) the rate curve the optimizer is using via GET / PUT /api/v1/rates — submitting your own 24-hour curve is the fastest way to give the optimizer a price signal to work with.
Appliance registration returns 403
Section titled “Appliance registration returns 403”Symptom: POST /api/v1/appliances returns 403 Upgrade required.
Cause: Free-tier accounts are limited to one HVAC appliance. You’ll get a 403 if you try to:
- Register a second appliance (any type)
- Register a non-HVAC device (EV charger, home battery, water heater)
Fix: Upgrade to Premium via the dashboard at hungrymachines.io/dashboard. Premium accounts can register unlimited appliances of any type.
Rate limit exceeded (429)
Section titled “Rate limit exceeded (429)”Symptom: POST /api/v1/readings returns 429 Rate limit exceeded.
Cause: The ceiling is 600 readings per active HVAC or dehumidifier appliance, per account, per UTC day (600 for an account with none registered yet). At 5-minute intervals one unit sends 288 a day, well under the cap. Large catch-up batches after an outage, or an uncoordinated set of sensors all pushing at once, can trip it.
Fix: Push on a steady cadence rather than in bursts. If you’re catching up after an outage, trim to the most recent readings. The ceiling scales with the number of active HVAC and dehumidifier appliances registered on the account; tag each reading with its appliance_id so it is attributed to the right unit.
Still stuck?
Section titled “Still stuck?”If none of the above resolves your issue:
- File a bug on GitHub Issues — include the endpoint, request shape, and full error response.
- Email us at info@hungrymachines.io for account-specific issues.