# Auth (Reference)

Programmatic auth endpoints: signup, profile fetch, profile update. Login and refresh live in Getting Started → Authentication.

For most clients the login + refresh pair in [Getting Started → Authentication](/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).

> **Caution**
>
> `POST /auth/signup` bypasses the billing and onboarding setup that the [hungrymachines.io](https://hungrymachines.io) signup flow handles. Use it only for test fixtures or short-lived dev accounts. Real users belong on the website signup flow.

---

## 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

Only `email` and `password` are required. Every profile field below is optional and can be set (or changed) later via `PATCH /auth/me`.

### Example

```bash
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

| 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`](/api-reference/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

When the account doesn't require email confirmation, the response is a full session:

```json
{
  "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

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:

```json
{
  "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

| 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](/api-reference/errors-and-limits/#error-shape). |
| 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

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

```bash
curl https://api.hungrymachines.io/auth/me \
  -H "Authorization: Bearer YOUR_TOKEN"
```

### Response (200)

```json
{
  "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

| 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). |

:::note[Signup fields are typed-or-defaulted on this first call]
When an account is created through the identity provider's own SDK rather than `POST /auth/signup`, nothing validated what went into the signup metadata. A value of the wrong type, or a `timezone` that is not a real IANA zone, is replaced with the field's default (`""`, `0`, `1`, `America/New_York`) on the profile this call creates, rather than being stored as sent. Set the real value with `PATCH /auth/me`, which validates and answers `422`.
:::

### 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

Update the profile. Send only the fields you want to change.

**Auth:** Required (Bearer JWT)

### 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

```bash
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

```json
{
  "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`](/api-reference/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. |

> **Tip**
>
> Calling `PATCH /auth/me` before `GET /auth/me` still creates the profile from the fields sent at signup (zip, size, pricing location, timezone, newsletter choice) — nothing is lost by calling it first.

### Response (200)

Returns the full profile object after the update — same shape as `GET /auth/me`.

### 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 |

Canonical page: https://docs.hungrymachines.io/api-reference/auth/
