Auth (Reference)
For most clients the login + refresh pair in Getting Started → Authentication is all you need. This page documents the endpoints that aren’t strictly part of the daily loop but are useful for programmatic onboarding (signup) and profile management (me).
POST /auth/signup
Section titled “POST /auth/signup”Create a new account. Optional profile fields are stored on the account and applied the first time the client calls GET /auth/me, so call GET /auth/me once after signup or login, before any other request.
Auth: None
Required vs. optional
Section titled “Required vs. optional”Only email and password are required. Every profile field below is optional and can be set (or changed) later via PATCH /auth/me.
Example
Section titled “Example”curl -X POST https://api.hungrymachines.io/auth/signup \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "securepassword", "location_zip": "92101", "home_size_sqft": 2000, "pricing_location": 1, "timezone": "America/Los_Angeles" }'Request body
Section titled “Request body”| Field | Type | Required | Notes |
|---|---|---|---|
email |
string | Yes | Must be unique |
password |
string | Yes | Subject to the account password policy |
location_zip |
string | No | US zip code, used for weather forecasts |
home_size_sqft |
integer | No | Used to estimate appliance power draw |
pricing_location |
integer | No | Rate-catalog id (see available_pricing_zones from GET /api/v1/rates; default 1) |
timezone |
string | No | IANA timezone name, for example America/Los_Angeles. 422 if it isn’t a real zone. |
newsletter_opt_in |
boolean | No | Newsletter checkbox state. Omitted means opted in. |
Send timezone at signup so the account starts on the right local day — every schedule is keyed to the account’s local midnight. An account created without one starts on America/New_York until PATCH /auth/me sets it.
Response (200) — session returned
Section titled “Response (200) — session returned”When the account doesn’t require email confirmation, the response is a full session:
{ "access_token": "eyJhbGciOi...", "refresh_token": "JKLrM3...", "expires_in": 3600, "expires_at": 1762450800, "token_type": "bearer", "user": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "user@example.com" }}Response (200) — email confirmation pending
Section titled “Response (200) — email confirmation pending”When email confirmation is required, the response is still 200 but the session fields (access_token, refresh_token, expires_in, expires_at, token_type) are absent. For example:
{ "user": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "user@example.com" }}Clients must handle both shapes: treat a 200 without access_token as “show a confirm-your-email screen”, and do not depend on any other key in that body.
Errors
Section titled “Errors”| Status | Detail | Cause |
|---|---|---|
| 400 | "User already registered" |
Email is already in use (or another signup failure message) |
| 422 | "Password should be at least 6 characters" |
Password policy rejection (string detail) |
| 422 | "timezone must be a valid IANA zone name, got '...'" |
timezone isn’t a real IANA zone |
| 422 | array detail |
email or password missing or not a string. See Errors, Limits & Stability. |
| 502 | "Auth service unreachable" |
Identity provider unreachable or failing |
| 503 | "Auth not configured" |
Auth not configured on the API |
The 400 and the password-policy 422 come from the identity provider and are passed through with its status code and message. Treat the codes above as the common cases, not a closed list: handle any other 4xx on this route as a failed signup and show its detail.
GET /auth/me
Section titled “GET /auth/me”Get the current user’s profile and subscription tier. Call it once right after signup or login, before any other request: the first call sets up the account’s profile and applies the optional fields sent at signup.
Auth: Required (Bearer JWT)
Example
Section titled “Example”curl https://api.hungrymachines.io/auth/me \ -H "Authorization: Bearer YOUR_TOKEN"Response (200)
Section titled “Response (200)”{ "user_id": "550e8400-e29b-41d4-a716-446655440000", "email": "user@example.com", "location_zip": "92101", "home_size_sqft": 2000, "pricing_location": 1, "timezone": "America/New_York", "subscription_tier": "free", "weather_entity_id": "weather.home"}Response fields
Section titled “Response fields”| Field | Type | Notes |
|---|---|---|
user_id |
string (UUID) | Account id (matches the JWT subject) |
email |
string | |
location_zip |
string | May be empty |
home_size_sqft |
integer | May be 0 |
pricing_location |
integer | Rate-catalog id (see available_pricing_zones) |
timezone |
string | IANA timezone |
subscription_tier |
string | "free" or "premium". Defaults to "free". |
weather_entity_id |
string | The Home Assistant weather.* entity the user picked for forecasts. Empty when unset (a fallback weather source is then used). |
Errors
Section titled “Errors”| Status | Detail | Cause |
|---|---|---|
| 401 | "Not authenticated" / "Invalid token" / "Token expired" / "Token missing subject" |
Missing, malformed, or expired token |
| 503 | "Database unavailable" / "Auth not configured" |
Backend dependency unavailable |
These 401 responses are the same on every route that requires auth.
PATCH /auth/me
Section titled “PATCH /auth/me”Update the profile. Send only the fields you want to change.
Auth: Required (Bearer JWT)
Required vs. optional
Section titled “Required vs. optional”All fields are optional — include only what you want to change. An omitted field, or one sent as null, is left unchanged; null does not clear a value. To clear location_zip or weather_entity_id, send "".
Example
Section titled “Example”curl -X PATCH https://api.hungrymachines.io/auth/me \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "location_zip": "94110", "home_size_sqft": 1800, "pricing_location": 3, "timezone": "America/Los_Angeles" }'Request body
Section titled “Request body”{ "location_zip": "94110", "home_size_sqft": 1800, "pricing_location": 3, "timezone": "America/Los_Angeles", "weather_entity_id": "weather.home"}| Field | Type | Required | Notes |
|---|---|---|---|
location_zip |
string | No | US zip code. Send "" to clear. Not checked as a real zip. |
home_size_sqft |
integer | No | |
pricing_location |
integer | No | A rate-catalog id from available_pricing_zones (GET /api/v1/rates). 422 if it isn’t one. |
timezone |
string | No | IANA timezone name, for example America/Los_Angeles. Schedules are keyed to it. 422 if it isn’t a real zone. |
weather_entity_id |
string | No | Home Assistant weather.* entity id. Send "" to clear. |
Response (200)
Section titled “Response (200)”Returns the full profile object after the update — same shape as GET /auth/me.
Errors
Section titled “Errors”| Status | Detail | Cause |
|---|---|---|
| 401 | as on GET /auth/me |
Missing, invalid or expired token |
| 422 | array detail |
A field has the wrong type |
| 422 | "timezone must be a valid IANA zone name, got '...'" |
timezone isn’t a real IANA zone |
| 422 | "pricing_location must be one of the catalog ids: [...]" |
pricing_location isn’t a known catalog id |
| 503 | "Database unavailable" |
Database not reachable |