Skip to content

Troubleshooting

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_token string with no trailing whitespace or line breaks.
  • Wrong header format. The header must be exactly Authorization: Bearer <access_token> — note the space after Bearer and no quotes around the token value.
  • Using the wrong token. access_token goes in the Authorization header. refresh_token is only for exchanging at POST /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.


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:

Terminal window
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.


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:

  1. Before the first plan: GET /api/v1/schedule returns source: "defaults", a comfort band built from your preferences, and setpoint_temps: null. GET /api/v1/schedules returns that appliance with "schedule": {} and source: "defaults".
  2. From the first plan on: source is "optimization" and setpoint_temps is a 48-element array, every day. Savings can read 0.0 on early plans, for example before any readings have arrived.
  3. 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.


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_level does not change it. If you have saved hourly bands that are tighter than that, widen them via PUT /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.


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.


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.


If none of the above resolves your issue: