Authentication (Reference)
This is reference documentation for paths that talk to the API directly — Custom Home Assistant integration and Build Your Own Client. If you installed the Energy Dashboard, you don’t need any of this — the dashboard handles tokens for you.
How accounts are created
Section titled “How accounts are created”Always sign up at hungrymachines.io/signup. That flow handles billing, subscription state, email verification, and account onboarding — none of which the API alone takes care of. Once your account exists, the two endpoints below are how you exchange your credentials for an API token.
Two tokens, two lifetimes
Section titled “Two tokens, two lifetimes”POST /auth/login and POST /auth/refresh both return the same shape:
{ "access_token": "eyJhbGciOiJFUzI1NiIs...", "refresh_token": "v1.Mfg2...", "expires_in": 3600, "expires_at": 1735689600, "token_type": "bearer", "user": { "id": "550e8400-...", "email": "you@example.com" }}| Token | Lifetime | Purpose |
|---|---|---|
access_token | 1 hour | Sent with every API request as Authorization: Bearer ... |
refresh_token | 30 days | Exchanged for a new access token without re-entering credentials |
The refresh token is rotated on each /auth/refresh call — always save the new one and discard the old.
Log in
Section titled “Log in”curl -X POST https://api.hungrymachines.io/auth/login \ -H "Content-Type: application/json" \ -d '{"email": "you@example.com", "password": "your-secure-password"}'Refresh an expired token
Section titled “Refresh an expired token”curl -X POST https://api.hungrymachines.io/auth/refresh \ -H "Content-Type: application/json" \ -d '{"refresh_token": "v1.Mfg2..."}'Attaching the access token
Section titled “Attaching the access token”Every authenticated request uses the Bearer scheme:
curl https://api.hungrymachines.io/auth/me \ -H "Authorization: Bearer $ACCESS_TOKEN"Full response shape in Auth Reference.
curl -X POST https://api.hungrymachines.io/api/v1/readings \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "readings": [...] }'Handling 401 errors
Section titled “Handling 401 errors”When the API returns 401 Unauthorized, the token is missing, malformed, or expired. The detail field tells you which:
{"detail": "Not authenticated"} // no Authorization header{"detail": "Token expired"} // past the 1-hour lifetime{"detail": "Invalid token"} // malformed or wrong signatureRecovery pattern for automated clients:
- Make the API call.
- If
401 Token expired, callPOST /auth/refreshwith your saved refresh token. - Save the new
access_tokenandrefresh_token. - Retry the original request with the new access token.
- If refresh itself returns
400 Invalid Refresh Token, the refresh token is stale (30-day expiry, or the user logged out elsewhere). Surface a “please sign in again” error.
Password reset
Section titled “Password reset”Users reset their password via hungrymachines.io/reset-password — the web flow sends a reset link to their registered email. Password reset is not currently exposed as an API endpoint.
Language-specific examples
Section titled “Language-specific examples”Python (httpx)
Section titled “Python (httpx)”import httpxfrom datetime import datetime, timezone
API = "https://api.hungrymachines.io"
class Client: def __init__(self, email: str, password: str): resp = httpx.post(f"{API}/auth/login", json={"email": email, "password": password}) resp.raise_for_status() session = resp.json() self.access_token = session["access_token"] self.refresh_token = session["refresh_token"] self.expires_at = session["expires_at"]
def _refresh(self): resp = httpx.post(f"{API}/auth/refresh", json={"refresh_token": self.refresh_token}) resp.raise_for_status() session = resp.json() self.access_token = session["access_token"] self.refresh_token = session["refresh_token"] self.expires_at = session["expires_at"]
def request(self, method: str, path: str, **kwargs): # Refresh proactively if we're within 5 minutes of expiry. now = int(datetime.now(timezone.utc).timestamp()) if now >= self.expires_at - 300: self._refresh() headers = kwargs.pop("headers", {}) headers["Authorization"] = f"Bearer {self.access_token}" return httpx.request(method, f"{API}{path}", headers=headers, **kwargs)
client = Client("you@example.com", "secure-password")resp = client.request("GET", "/auth/me")print(resp.json())JavaScript (fetch)
Section titled “JavaScript (fetch)”class HMClient { constructor(session) { this.access_token = session.access_token; this.refresh_token = session.refresh_token; this.expires_at = session.expires_at; }
static async login(email, password) { const resp = await fetch('https://api.hungrymachines.io/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }); if (!resp.ok) throw new Error('login failed'); return new HMClient(await resp.json()); }
async _refresh() { const resp = await fetch('https://api.hungrymachines.io/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh_token: this.refresh_token }), }); if (!resp.ok) throw new Error('refresh failed — sign in again'); const session = await resp.json(); this.access_token = session.access_token; this.refresh_token = session.refresh_token; this.expires_at = session.expires_at; }
async request(path, opts = {}) { if (Math.floor(Date.now() / 1000) >= this.expires_at - 300) { await this._refresh(); } return fetch(`https://api.hungrymachines.io${path}`, { ...opts, headers: { ...opts.headers, Authorization: `Bearer ${this.access_token}` }, }); }}
const client = await HMClient.login('you@example.com', 'secure-password');const me = await (await client.request('/auth/me')).json();Next steps
Section titled “Next steps”- Custom Home Assistant integration — REST sensors and rest_command setup
- Build Your Own Client — full integration loop with Python + ESP32 examples
- API Reference — Readings — start pushing sensor data