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 at$SUPABASE_URL/auth/v1/token?grant_type=refresh_token.
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: Supabase 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 GET /api/v1/schedule returns source: "defaults" with a flat comfort band and zero savings.
Cause: The optimizer needs about 3 days of continuous readings (72 hourly buckets, with no more than a couple of gap-filled hours) to build your first per-user thermal model. Until the model is fit, the schedule endpoint returns safe defaults based on your preferences and setpoint_temps is null.
What to expect:
- Days 1–2:
source: "defaults", flat comfort band,model_confidence: null,setpoint_temps: null. - Day 3+: Once you have ~72 hourly buckets of data, the daily initial-fit job (06:30 UTC) builds your first per-user model. The nightly slice that follows produces your first
source: "optimization"schedule. - Sunday 02:00 UTC weekly refit: All active users get a refreshed model from the last 14 days of readings.
Fix: Keep pushing readings consistently. The defaults are safe to apply — they maintain your comfort settings but won’t save energy. When source is "defaults", apply the band midpoint as your setpoint: (high_temps[idx] + low_temps[idx]) / 2. Once the model is ready, setpoint_temps becomes a 48-element array and optimized schedules appear automatically.
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:
- Savings level 1 (tight). With a ±2°F band, the optimizer has very little room to shift loads. Try increasing to savings level 2 (±6°F) or 3 (±12°F) 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 savings_level 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: max 300 readings per user per day.
Cause: Your client is pushing more than 300 readings in a 24-hour window. At 5-minute intervals, a single sensor generates 288 readings/day — within the limit. But batching catch-up readings after an outage or running multiple sensors without coordination can push you over.
Fix: Implement client-side rate tracking. If you have multiple sensors, coordinate their reporting windows. If you’re batching after an outage, spread the catch-up over multiple days or trim to the most recent 300 readings.
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.