Pause on-peak.
Charge off-peak.

Stalls and depots follow the tariff clock at every ZIP. Period names and boundaries. Not ¢/kWh. Not a hardcoded 4–9 PM.

Don’t Hardcode Peak Hours

Vehicles come back at shift-end — often the same hours the utility bills as on-peak. A weekly 9 PM–6 AM window in the charger software is one California weekday in summer.

Weekly window
charge_after = time(21, 0)
pause_after  = time(6, 0)

if now.time() >= charge_after or now.time() < pause_after:
    resume()
else:
    pause()

Looks fine in June. Wrong in January, on a holiday, and at the depot that isn’t 4–9.

From the tariff
period = results[0]["period"]

if period in ("on_peak", "critical_peak"):
    pause_stalls(zip)
elif period in ("off_peak", "super_off_peak"):
    resume_stalls(zip)

The period is the clock the utility bills. Chargers follow it.

ZIP → current period → pause / resume

One REST call for the live period. A forward timeline for overnight dwell. A webhook when the period actually flips.

1

Pass a ZIP

Any U.S. ZIP, optional street if the ZIP is shared. customer_class=ev for EV tariffs; commercial when the depot is on a general C&I plan.

2

Read the period

off_peak, on_peak, mid_peak, super_off_peak, or critical_peak — plus next_change_at.

3

Pause or resume

Hold stalls through on-peak. Resume on off-peak and super-off-peak. Your CSMS still owns power caps, queues, and departure SOC.

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

Current, forward, then the flip

Live period for the loop. A timeline for overnight dwell. A signed POST when the period actually changes.

One site, current period

Refresh at next_change_at instead of polling. Free is current period only. Need tonight’s windows? /query/forward starts on Builder (24 h).

python · one depot
# GET /api/v1/query/current?zip_code=94103&customer_class=ev
period = results[0]["period"]
next_at = results[0]["next_change_at"]

if period in ("on_peak", "critical_peak"):
    pause_stalls()
elif period in ("off_peak", "super_off_peak"):
    resume_stalls()

refresh_at(next_at)  # do not poll every 15 minutes

The peak-start POST

Paid plans. One endpoint can watch every location. Deliveries do not count as API calls. Signature and retries: /webhooks/.

python · one endpoint
# POST /hooks/tou  — verify Tou-Signature first
# /docs/#webhooks

@app.post("/hooks/tou")
def on_period_changed():
    event = request.get_json()
    if event["period"] in ("on_peak", "critical_peak"):
        pause_stalls(event["zip_code"])
    elif event["period"] in ("off_peak", "super_off_peak"):
        resume_stalls(event["zip_code"])
    return ("", 200)

The tariff clock, put to work

Overnight depot

Shift-end is often on-peak. Hold until off_peak / super_off_peak, then fill before morning dispatch. Forward gives the window.

Public stalls

Pause when period becomes on_peak. Resume when that ZIP’s utility goes off-peak. Same handler, many sites.

EV-specific tariffs

Pass customer_class=ev for plans like PG&E BEV or SCE TOU-EV-8. Default residential is the wrong table.

Don’t poll

A 15-minute loop is ~2,880 calls per site per month. Fifty sites on that loop need Growth. The same fifty fit Builder on webhooks.

Demand stays yours

We tell you when on-peak is. We don’t compute kW demand charges, site caps, or departure SOC. Put your EMS on the clock.

Home Level 2

A house that refreshes at next_change_at fits Free. That’s the Home Assistant recipe, not this page.

Every site has a different clock

A national pack of stalls, a last-mile depot in three states, or a transit yard next to a public lot — same job at every ZIP you operate. A summer weekday is not a winter holiday. PG&E BEV is not SCE TOU-EV-8.

Watch locations with a signed tou.period.changed POST. Plan overnight charge windows with /query/forward (24 h on Builder, 7 days on Growth, 30 days on Business). Unique location = distinct ZIP this billing cycle, not polls.

We still don’t send ¢/kWh or kW demand. Bill savings, site caps, and vehicle readiness stay yours. Enterprise is unlimited locations and calls from $1,200/mo, with a dedicated engineer and a contractual uptime SLA.

GET /query/current
Live period + next_change_at per ZIP. customer_class=ev for EV tariffs; commercial when the depot is on a general C&I plan.
GET /query/forward
Concrete timeline: period, start_at, end_at. Seasons, weekdays, and holidays already applied. Schedule the overnight dwell; don’t re-encode the tariff.
tou.period.changed
Webhook on paid plans. One POST at peak start — and every other boundary — for each watched ZIP. No 15-minute fleet poll.
locations
Builder 75 · Growth 300 · Business 1,500 · Enterprise unlimited. Hitting the cap is a 429, not an overage bill.

Frequently asked

  1. How do I charge an EV fleet on off-peak?

    Call GET /api/v1/query/current with the depot ZIP and customer_class=ev or commercial. Pause stalls when results[0].period is on_peak or critical_peak. Resume on off_peak / super_off_peak. Paid plans add /query/forward for overnight windows and webhooks for the flip itself.

  2. Does this return ¢/kWh or demand charges?

    No. tou.tools is the timing layer: period names and boundaries. Prices live in riders and baselines. Demand is kW on the bill, not a period name. Put your own energy, demand, and departure-SOC model on top.

  3. Which plan for a 50-site charger fleet?

    Builder, on webhooks. 50 sites fit the 75-location cap. One endpoint can watch every location, and deliveries do not count as API calls. Polling those 50 sites every 15 minutes is ~144,000 calls/month and needs Growth. Business is about 1,500 sites. Unlimited is Enterprise; contact sales.

  4. When do I pass customer_class=ev?

    When the site is on an EV-specific tariff — PG&E BEV, SCE TOU-EV-8, AEP VA-LFEVC, and similar. Default residential is the wrong table. If the depot is on a general commercial TOU plan, pass commercial. customer_class is on every plan, including Free.

  5. Is this for a home Level 2 charger?

    This page is stalls and depots. A house that refreshes at next_change_at fits Free and is the Home Assistant recipe.

  6. Will this work with my utility?

    If we have a seeded TOU plan for the utility that serves the 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 the bill, or pass street / city / state.

Start Free, Or Talk Enterprise

No credit card to try one ZIP. Contact sales when the fleet needs unlimited locations and an SLA.