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.
Stalls and depots follow the tariff clock at every ZIP. Period names and boundaries. Not ¢/kWh. Not a hardcoded 4–9 PM.
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.
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.
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.
How it works
One REST call for the live period. A forward timeline for overnight dwell. A webhook when the period actually flips.
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.
off_peak, on_peak, mid_peak, super_off_peak, or critical_peak — plus next_change_at.
Hold stalls through on-peak. Resume on off-peak and super-off-peak. Your CSMS still owns power caps, queues, and departure SOC.
Last-mile, school bus, transit, trucking. Vehicles dwell overnight. Fill that window off-peak, not at 5 PM plug-in.
Wire it inCharge-point networks pause when that site’s utility peaks. Resume on off-peak and super-off-peak.
Wire it inPG&E is not SCE is not ConEd. Each ZIP has its own clock. Webhooks, not a 15-minute fleet poll.
FleetsInstall
Live period for the loop. A timeline for overnight dwell. A signed POST when the period actually changes.
Refresh at next_change_at instead of polling. Free is current period only.
Need tonight’s windows? /query/forward starts on Builder (24 h).
# 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
Paid plans. One endpoint can watch every location. Deliveries do not count as API calls. Signature and retries: /webhooks/.
# 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)
What to fire
Shift-end is often on-peak. Hold until off_peak / super_off_peak, then fill before morning dispatch. Forward gives the window.
Pause when period becomes on_peak. Resume when that ZIP’s utility goes off-peak. Same handler, many sites.
Pass customer_class=ev for plans like PG&E BEV or SCE TOU-EV-8. Default residential is the wrong table.
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.
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.
A house that refreshes at next_change_at fits Free. That’s the Home Assistant recipe, not this page.
Multi-territory fleets
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.
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.
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.
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.
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.
This page is stalls and depots. A house that refreshes at next_change_at fits Free and is the Home Assistant recipe.
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.
No credit card to try one ZIP. Contact sales when the fleet needs unlimited locations and an SLA.