Time-of-Use
as a live Home Assistant sensor

ZIP in, current peak / off-peak period out. Automate EV charging, HVAC, and loads off the tariff clock — not a hardcoded 4–9 PM that drifts every season.

Don’t Hardcode Peak Hours

Home Assistant forums are full of now().hour and schedule helpers. They miss holidays, season flips, DST, and the day the utility refiles the tariff.

Hardcoded
template:
  - binary_sensor:
      - name: Peak hours
        state: >
          {{ now().weekday() < 5
             and 16 <= now().hour < 21 }}

Looks fine in June. Wrong in January, on a holiday, and the week the peak window moves.

From the tariff
automation:
  - alias: Charge EV when off-peak
    triggers:
      - trigger: state
        entity_id: binary_sensor.off_peak
        to: "on"

The period is a sensor. Automations follow the clock the utility actually bills.

ZIP → current period → automation

One REST call. Home Assistant already speaks REST. No custom component required.

1

Pass a ZIP

Any U.S. ZIP, optional street if the ZIP is shared. Residential is the default customer class.

2

Read the period

off_peak, on_peak, mid_peak, super_off_peak, or critical_peak — plus when it next changes.

3

Trigger on state

EV charger, HVAC, dishwasher, battery reserve, or the Energy dashboard tariff selector.

GET /api/v1/query/current?zip_code=94103
{
"confidence": "unique",
"results": [{
"period": "off_peak",
"next_change_at": "2026-05-16T16:00:00-07:00"
}]
}

Wire it in with REST

Copy into Home Assistant. Swap the ZIP. Reload REST entities and template entities.

1. Keep the key out of git

Home Assistant reads !secret from secrets.yaml. Include the word Bearer.

secrets.yaml
tou_api_key: Bearer tou_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

2. Current period as sensors

One HTTP call, three entities: the period, a timestamp for the next boundary, and an on/off for cheap hours. scan_interval is a safety net. The automation in step 3 does the real refresh.

configuration.yaml
rest:
  - resource: https://tou.tools/api/v1/query/current?zip_code=94103
    scan_interval: 21600
    headers:
      Authorization: !secret tou_api_key
      Accept: application/json
    sensor:
      - name: TOU period
        unique_id: tou_period
        value_template: "{{ value_json.results[0].period }}"
        json_attributes_path: "$.results[0]"
        json_attributes:
          - next_change_at

template:
  - sensor:
      - name: TOU next change
        unique_id: tou_next_change
        device_class: timestamp
        state: "{{ state_attr('sensor.tou_period', 'next_change_at') }}"
  - binary_sensor:
      - name: Off-peak
        unique_id: tou_off_peak
        state: "{{ states('sensor.tou_period') in ['off_peak', 'super_off_peak'] }}"

3. Flip at the boundary, then automate

A time trigger on the timestamp sensor fires just after the tariff changes. Replace switch.ev_charger with your load. Drop the list into automations.yaml, or paste one automation at a time into the editor.

automations.yaml
- alias: Refresh TOU at the next period change
  triggers:
    - trigger: time
      at:
        entity_id: sensor.tou_next_change
        offset: "00:00:45"
  actions:
    - action: homeassistant.update_entity
      target:
        entity_id: sensor.tou_period

- alias: Charge EV when off-peak
  triggers:
    - trigger: state
      entity_id: binary_sensor.off_peak
      to: "on"
  actions:
    - action: switch.turn_on
      target:
        entity_id: switch.ev_charger

- alias: Sync Energy dashboard tariff
  triggers:
    - trigger: state
      entity_id: sensor.tou_period
  actions:
    - action: select.select_option
      target:
        entity_id: select.daily_energy
      data:
        option: >
          {{ 'offpeak' if states('sensor.tou_period') in
             ['off_peak', 'super_off_peak'] else 'peak' }}

HA peak hours automation, without the clock math

EV charging

Turn the charger on when binary_sensor.off_peak goes on. Hold through super-off-peak. Stop at on-peak.

HVAC pre-cool

Drop the setpoint an hour before next_change_at when the next period is on-peak, then coast.

Wet loads

Delay the dishwasher, washer, and water heater until off-peak. Same binary sensor, different switches.

Home battery

Charge through off-peak, raise reserve at peak start. Period names only — no ¢/kWh from this API.

Energy dashboard

Drive utility_meter tariff selects from sensor.tou_period instead of time triggers at 16:00.

Notify before peak

When sensor.tou_next_change is under an hour away and the current period is off-peak, ping the phone.

The same API as a dedicated integration

A custom Home Assistant component — tou.tools (Time-of-Use) — wraps this exact flow: API key + ZIP in a config UI, an enum period sensor, an off-peak binary sensor, and a coordinator that reschedules itself for just after next_change_at.

REST above is the path that works on any Home Assistant today, no extra component. The custom integration is the same sensors without YAML, and it stays inside the free tier by polling only at boundaries. Install from REST; switch later if you want the UI.

sensor.tou_period
Enum: off_peak, mid_peak, on_peak, super_off_peak, critical_peak. Attributes include next_change_at, rate plan, and utility.
binary_sensor.off_peak
On while the period is off_peak or super_off_peak. The trigger most automations actually want.
sensor.tou_next_change
Timestamp device class. Fire a time trigger at the boundary instead of polling every 15 minutes.

Frequently asked

  1. How do I get time-of-use peak hours into Home Assistant?

    Call GET /api/v1/query/current with your ZIP from a REST sensor. results[0].period is the live period. Automate on that, or on a binary sensor that is on during off-peak and super-off-peak. YAML is on this page.

  2. Does this return ¢/kWh?

    No. tou.tools is the timing layer: period names and boundaries. Prices live in riders, baselines, and customer class. Put your own rates on the Energy dashboard or in utility_meter if you need a dollar figure.

  3. Will this work with my utility?

    If we have a seeded TOU plan for the utility that serves your ZIP, yes. See coverage. When a ZIP is ambiguous (common in Texas / ERCOT), the API returns every candidate plan instead of guessing — pick the one on your bill, or pass street / city / state.

  4. How many API calls does a home use?

    Refreshing at next_change_at is a handful of calls per day and fits the free plan (250/month, 3 locations). Polling every 15 minutes is ~2,880/month. The YAML on this page does the former.

  5. Can I drive utility_meter from this?

    Yes. On a state change of sensor.tou_period, call select.select_option on your meter’s tariff selector. Map off_peak / super_off_peak to your cheap tariff name. The third automation in the recipe is that mapping.

Start With a Free Key

No credit card. 250 calls/month. Enough for a house if you refresh at next_change_at (a few calls a day).

Get Free API Key