# Weather

Push your Home Assistant weather forecast to Hungry Machines via POST /api/v1/weather so the optimizer plans against your local weather instead of a generic fallback.

If your Home Assistant has a `weather.*` entity wired up (most installs do), push the hourly forecast once daily so the optimizer plans against your local weather rather than a generic fallback. The Hungry Machines app does this for you automatically; custom integrations should set up a daily `rest_command`.

Each push is stored for the next optimization run and retained as history that later helps fill outdoor-temperature gaps. If no fresh push is available when the overnight run happens (older than 24 hours, or never pushed), a fallback weather source is used for that account.

## POST /api/v1/weather

**Auth:** Required (Bearer JWT)

### Required vs. optional

Only `forecast.hourly_temps_f` (24–72 values) is required. `forecast.forecast_date` and the humidity, wind, cloud-cover, precipitation and solar-irradiance arrays are optional — send the ones your weather source exposes.

### Example

```bash
curl -X POST https://api.hungrymachines.io/api/v1/weather \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "forecast": {
      "hourly_temps_f": [54.2, 53.8, "...(24-72 values)"],
      "hourly_humidity": [62, 64, "..."],
      "hourly_wind_mph":  [3.1, 2.8, "..."]
    }
  }'
```

### Request body

```json
{
  "forecast": {
    "hourly_temps_f": [54.2, 53.8, 53.5, "..."],
    "hourly_humidity": [62, 64, 65, "..."],
    "hourly_wind_mph": [3.1, 2.8, 2.5, "..."]
  }
}
```

| Field | Type | Required | Validation |
|---|---|---|---|
| `forecast.hourly_temps_f` | array[float] | Yes | 24–72 hourly values, each in `[-50, 150]` °F. Element 0 is local midnight (hour 0) of `forecast_date` in the account's timezone; the array walks forward one hour per element. |
| `forecast.forecast_date` | string | No | `YYYY-MM-DD`: the local calendar date that element 0 covers. Defaults to today in the account's timezone. Validated as an ISO date; a malformed value is a `400`. |
| `forecast.hourly_humidity` | array[float] \| null | No | Same length as `hourly_temps_f`. Each value in `[0, 100]` percent. Omit or send `null` to skip. |
| `forecast.hourly_wind_mph` | array[float] \| null | No | Same length as `hourly_temps_f`. Each value in `[0, 200]` mph. Omit or send `null` to skip. |
| `forecast.hourly_cloud_coverage_pct` | array[float] \| null | No | Same length as `hourly_temps_f`. Each value in `[0, 100]` percent. Omit or send `null` to skip. |
| `forecast.hourly_precipitation_mm` | array[float] \| null | No | Same length as `hourly_temps_f`. Each value in `[0, 300]` mm. Omit or send `null` to skip. |
| `forecast.hourly_solar_irradiance_w` | array[float] \| null | No | Same length as `hourly_temps_f`. Each value in `[0, 1500]` W/m². Omit or send `null` to skip. |

> **Tip**
>
> The minimum 24 hours covers the next day's nightly optimization. Pushing 48–72 hours is fine — Home Assistant's `weather.get_forecasts` typically returns 3 days, and the extra hours give the weekly model refit more outdoor-temp data to gap-fill from.

### Response (201)

```json
{
  "accepted_hours": 48,
  "pushed_at": "2025-11-18T03:30:00+00:00"
}
```

| Field | Type | Notes |
|---|---|---|
| `accepted_hours` | integer | Number of hourly entries stored from `hourly_temps_f` |
| `pushed_at` | string | UTC ISO 8601 timestamp when the row was saved |

### Errors

| Status | Detail | Cause |
|---|---|---|
| 400 | `"<name> must contain between 24 and 72 hourly values"` | Array length out of bounds. `<name>` is the field path, e.g. `forecast.hourly_temps_f`. |
| 400 | `"<name>[<i>]=<v> is outside the allowed range [<min>, <max>]"` | Value out of range |
| 400 | `"<name>[<i>] is not a finite number"` | Non-finite value |
| 400 | `"<name> length must match hourly_temps_f"` | An optional array's length disagrees with `hourly_temps_f` |
| 400 | `"forecast.forecast_date must be an ISO date (YYYY-MM-DD)"` | `forecast_date` is not a valid ISO date |
| 401 | `"Not authenticated"` | Missing or invalid token |
| 422 | array `detail` | `forecast` or `forecast.hourly_temps_f` missing, or an array element that is not a number. See [Errors, Limits & Stability](/api-reference/errors-and-limits/#error-shape). |
| 503 | `"Database unavailable"` | Database not reachable |
| 503 | `"Failed to store weather forecast"` | DB write failed |

### When to push

Push **once per day**, before the account's overnight optimization run. Any push from the last 24 hours counts as fresh, so missing a single day simply falls back to a server-side weather source (or the most recent prior push).

A typical Home Assistant `rest_command` + automation:

```yaml
rest_command:
  hm_push_weather:
    url: "https://api.hungrymachines.io/api/v1/weather"
    method: POST
    headers:
      Authorization: "Bearer !secret hm_access_token"
      Content-Type: "application/json"
    payload: >
      {
        "forecast": {
          "hourly_temps_f": {{ state_attr('weather.home', 'forecast') | map(attribute='temperature') | list | tojson }}
        }
      }

automation:
  - alias: "HM — push weather forecast at 03:30 UTC"
    trigger:
      - platform: time
        at: "03:30:00"
    action:
      - service: rest_command.hm_push_weather
```

Adjust the entity (`weather.home`) and forecast attribute path to match what your weather integration exposes. A typical HA weather integration returns `forecast` as a list of dicts with a `temperature` field; some integrations use `templow`/`temperature` per day instead — verify in Developer Tools → States first.

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