BESS
Battery-storage telemetry and the dispatch-proposal approval workflow.
The BESS surface has two halves: read everything about a battery unit (bess:read), and drive the dispatch-proposal workflow (bess:write, a control scope). Both require the bess module.
Raw hardware control is not in the API — by design. Power on/off, breaker, grid-mode, fault-reset, and protection/setup writes are owner-platform-only. bess:write covers dispatch proposals only: ask the AI for a schedule, then approve/reject/edit it.
Reads
Everything about a unit is readable:
- Inventory & live state —
bess,bess/fleet/summary,bess/{id},bess/{id}/current,bess/{id}/dashboard. - History —
bess/{id}/history,bess/{id}/pcs/history,bess/{id}/dispatch-log,bess/{id}/control-log(allstart/end+limit). - Internals —
clusters,clusters/{idx}(cell arrays),pcs/current,pcs/faults,envelope(charge/discharge + SOC),protection,setup. - Alarms —
alarms/states(?only_set&severity),alarms/history(?code&limit),alarms/counts. - Schedules —
bess/{id}/schedules(?target_date),bess/schedules/{schedule_id}(one schedule with its hours) andbess/{id}/deviations(schedule-vs-actual).
See the BESS reference for every endpoint and its parameters.
The dispatch-approval workflow
Dispatch is a propose → review → approve loop. Nothing is dispatched to the battery until you approve a schedule.
- Optimize.
POST …/bess/{bess_id}/optimize(orPOST …/bess/optimize-all) runs the AI dispatch optimizer and produces a proposed schedule — a set of hourly charge/discharge targets. This does not command the battery. - Review. Read the proposal with
GET …/bess/schedules/{schedule_id}(or…/bess/{bess_id}/schedules?target_date=…). Inspect each hour and the projected deviations. - (Optional) edit an hour.
PUT …/bess/schedules/{schedule_id}/hours/{hour}revises a single hour of a proposed schedule before approval. - Approve or reject.
POST …/bess/schedules/{schedule_id}/approveaccepts the proposal;…/rejectdiscards it.POST …/bess/schedules/approve-all?target_date=…approves every pending schedule for a day.
# 1. Ask the AI to optimize one unit
curl -X POST "https://api.owner.dynvolt.com/v1/sites/pv-ljubas/bess/1/optimize" \
-H "Authorization: Bearer $DYNVOLT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"
# 2. Review the proposed schedule (id from the response above)
curl "https://api.owner.dynvolt.com/v1/sites/pv-ljubas/bess/schedules/77" \
-H "Authorization: Bearer $DYNVOLT_API_KEY"
# 3. Approve it
curl -X POST "https://api.owner.dynvolt.com/v1/sites/pv-ljubas/bess/schedules/77/approve" \
-H "Authorization: Bearer $DYNVOLT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import uuid
def hdr():
return {"Idempotency-Key": str(uuid.uuid4())}
# 1. Optimize → proposed schedule
proposal = client.post("/v1/sites/pv-ljubas/bess/1/optimize", headers=hdr()).json()
schedule_id = proposal["schedule_id"]
# 2. Review
schedule = client.get(f"/v1/sites/pv-ljubas/bess/schedules/{schedule_id}").json()
# 3. Approve (or .../reject)
client.post(
f"/v1/sites/pv-ljubas/bess/schedules/{schedule_id}/approve",
headers=hdr(),
)All bess:write calls accept an Idempotency-Key and return 409 conflict_safe_mode while the site is in commissioning safe mode.

