API reference
Dispatch
Telling an asset what to do: immediate dispatch and its history, scheduled dispatch and the anticipated dispatch forecast. Immediate dispatch is an unrestricted override, so read its description before using it.
GET/v1/assets/{asset_id}/dispatch/immediate
Get immediate dispatch history
dispatch.read
Get a history of immediate dispatch issued for an asset.
Time filtering is overlap-based and uses half-open window semantics. With both start_time and end_time, the endpoint returns dispatch intervals that overlap [start_time, end_time).
Boundary behavior:
- a dispatch ending exactly at
start_timeis excluded - a dispatch starting exactly at
end_timeis excluded
set_power_mw is the dispatch set point in MW. Positive values correspond to injection into the grid, negative values to absorption from the grid.
History is limited to the last 30 days.
Path parameters
- asset_idREQUIRED
- string
Query parameters
- sort_by
- enum
start_timeend_timeField to sort the results by - sort_order
- enum
ascdescOrder to sort the results by, either 'asc' or 'desc' - start_time
- string<date-time>Return immediate dispatch intervals whose end_time is strictly after this timestamp. With end_time, the API matches intervals overlapping the half-open window [start_time, end_time).
- end_time
- string<date-time>Return immediate dispatch intervals whose start_time is strictly before this timestamp. With start_time, the API matches intervals overlapping the half-open window [start_time, end_time).
curl "https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/immediate" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/immediate",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/immediate", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"dispatches": [
{
"start_time": "2026-09-10T13:30:00Z",
"end_time": "2026-09-10T13:30:00Z",
"set_power_mw": 0
}
]
}- 401no valid token
- 403scope missing
- 404unknown or not visible
- 422validation failed
- 429rate limited
POST/v1/assets/{asset_id}/dispatch/immediate
Set immediate dispatch power (unrestricted override)
dispatch.write
Override the dispatch of an asset for a bounded window, bypassing every safety safeguard.
Unrestricted emergency override — use with extreme care.
This endpoint deliberately bypasses every platform safeguard: declared unavailabilities, power limits, and regulatory or market restrictions are neither checked nor enforced. The platform attempts to execute the dispatch regardless of the asset's declared state, schedule, or market obligations. Executing it can violate market regulations and cause system imbalances. The caller carries full responsibility for ensuring the requested set point is physically safe for the asset and permissible in the current situation. Use only when strictly necessary, e.g. on explicit TSO instruction or in an emergency.
Behavior:
- The dispatch becomes active at
start_time(defaults to now if omitted) and runs untilend_time(required). - At
end_time, the asset is automatically reset to its currently scheduled wholesale power. Internally, two events are published: anenableatstart_timeand an automaticresetatend_timethat queries the current schedule and dispatches it. - While an immediate dispatch is active, conflicting wholesale schedule events for the same asset are rejected by the dispatcher.
- Submitting a new request while an immediate dispatch is already active will override the existing one.
Constraints (request validation and one plausibility bound — none are safety checks):
set_power_mwmust not exceed the asset'susable_power_capacityin magnitude (spec ID-04). This is a static sanity bound, not a safety check: it refuses a setpoint the asset could not physically produce under any circumstances — a typo of 500 instead of 50 — and consults nothing about the asset's live state, obligations or restrictions. Until now this endpoint had no magnitude bound at all. While the asset is in phase 1 of the validation rollout (strict_validationfalse) a breach is reported inwarningsand the dispatch is still stored; in phase 2 it is rejected. The historical 2x-capacity bound is always rejected. An asset with no recorded capacity is not refused — the gap is logged instead, since declining an emergency override over a missing parameter would be worse than not bounding it.end_timeis required and must be in the future.start_timeis optional. If provided, it must not be more than 1 second in the past (this allowance covers round-trip latency only and prevents backdating).- The window
end_time - start_timemust be at least the configured minimum (IMMEDIATE_DISPATCH_MIN_WINDOW_SECONDS, default 10s). Requests with a shorter effective window are rejected with 400.
set_power_mw is the dispatch set point in MW. Positive values correspond to injection into the grid, negative values to absorption from the grid.
Path parameters
- asset_idREQUIRED
- string
Request body · required
- start_time
- string<date-time>Optional start time of the dispatch in UTC. Defaults to the server's current time when omitted, and must be within 1s of it — immediate dispatches always start now; there is no future scheduling.
- end_timeREQUIRED
- string<date-time>End time of the dispatch in UTC. Required.
- set_power_mwREQUIRED
- numberSet point for the dispatch in MW. Positive corresponds to injection of energy into the grid, negative corresponds to absorption of energy from the grid.
curl -X POST "https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/immediate" \
-H "Authorization: Bearer $EBX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"start_time":"2026-09-10T13:30:00Z","end_time":"2026-09-10T13:30:00Z","set_power_mw":55.1}'import httpx
r = httpx.post(
"https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/immediate",
headers={"Authorization": f"Bearer {token}"},
json={
"start_time": "2026-09-10T13:30:00Z",
"end_time": "2026-09-10T13:30:00Z",
"set_power_mw": 55.1,
},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/immediate", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"start_time":"2026-09-10T13:30:00Z","end_time":"2026-09-10T13:30:00Z","set_power_mw":55.1}),
});
const data = await res.json();{
"start_time": "2026-09-10T13:30:00Z",
"end_time": "2026-09-10T13:30:00Z",
"set_power_mw": 0,
"warnings": [
"<string>"
]
}- 400malformed request
- 401no valid token
- 403scope missing
- 404unknown or not visible
- 409state conflict
- 422validation failed
- 429rate limited
PUT/v1/assets/{asset_id}/dispatch/immediate/disable
Disable immediate dispatch
dispatch.write
Disable the active immediate dispatch for a specific asset.
Sets the active dispatch's end_time to the current time; the dispatcher derives the reset from the operational-state snapshot and restores the scheduled wholesale power. Returns the now-closed dispatch record. Returns 404 if no dispatch was active.
Path parameters
- asset_idREQUIRED
- string
curl -X PUT "https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/immediate/disable" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.put(
"https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/immediate/disable",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/immediate/disable", {
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"start_time": "2026-09-10T13:30:00Z",
"end_time": "2026-09-10T13:30:00Z",
"set_power_mw": 0
}- 400malformed request
- 401no valid token
- 403scope missing
- 404unknown or not visible
- 409state conflict
- 429rate limited
GET/v1/assets/{asset_id}/dispatch/scheduled
Get scheduled dispatch
dispatch.read
Get the current dispatch schedule for an asset.
The schedule is returned for the specified date and time range. If no start or end time is provided, the entire day is considered.
The returned schedule provides a continuous view of the asset's dispatch schedule. For each schedule event, the response includes the power in MW, delivery day, and start time of the delivery. The power is held same until the next schedule event within the same 15-minute block interval.
| Deliver Day | Delivery Block Start Time | Start Time | Power (MW) |
|---|---|---|---|
| 2025-01-01 | 00:00:00 | 00:00:00 | 0 |
| 2025-01-01 | 00:00:00 | 00:09:00 | 10 |
| 2025-01-01 | 00:30:00 | 00:30:00 | -20 |
| 2025-01-01 | 00:30:00 | 00:32:00 | -5 |
| 2025-01-01 | 00:45:00 | 00:45:00 | 0 |
| 2025-01-01 | 01:00:00 | 01:00:00 | 30 |
At 00:00, the power is 0 MW until 00:09, when it changes to 10 MW. At 00:15, the power is reset to 0 MW as we move to the next 15-minute block. At 00:30, the power changes to -20 MW and remains until 00:32, when it changes to -5 MW. At 00:45, the power is set to 0 MW and held until 01:00, when it changes to 30 MW. At 01:15, the power is reset to 0 MW as we move to the next 15-minute block.
Querying can be done exclusively on 15-minute block intervals (i.e. on 15 minute marks, e.g. between 00:00:00 and 18:45:00, but not between 00:10:00 and 18:32:00).
set_power_mw (deprecated alias of bess_power_mw) is the scheduled BESS power in MW for each delivery time slot. Until per-mode persistence lands, responses report control_mode=bess_only and poc_power_mw=null for all events.
Path parameters
- asset_idREQUIRED
- string
Query parameters
- start_time
- string<date-time>Start Time in ISO8601 format
- end_time
- string<date-time>End Time in ISO8601 format
curl "https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/scheduled" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/scheduled",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/scheduled", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"schedules": [
{
"control_mode": "passive",
"set_power_mw": 0,
"bess_power_mw": 0,
"poc_power_mw": 0,
"delivery_time": "2026-09-10T13:30:00Z",
"set_time": "2026-09-10T13:30:00Z"
}
],
"warnings": [
"<string>"
]
}- 401no valid token
- 403scope missing
- 404unknown or not visible
- 422validation failed
- 429rate limited
PUT/v1/assets/{asset_id}/dispatch/scheduled
Update scheduled dispatch
dispatch.write
Update the scheduled dispatch for a specific asset.
The schedule is a list of targets: a BESS power in MW (bess_power_mw; the former power_mw is a deprecated alias) and the time from which it applies (delivery_time). The asset's power is steered towards the most recent target at the asset's default ramp rate. Targets can be submitted at up to per-second resolution (sub-second precision is truncated), e.g. to make the asset ramp slower than its default rate.
Control modes (co-located PV+BESS sites): each target carries a control_mode deciding which setpoints apply (sign convention: import/charging negative, export/discharging positive):
control_mode | bess_power_mw | poc_power_mw |
|---|---|---|
passive | must be null/zero | must be null/zero |
bess_only (default when omitted) | required | must be null/zero |
bess_and_poc | required | required — PoC target has priority |
poc_only | must be null/zero | required |
Requests violating this matrix are rejected with 422. pv_power_mw is reserved and must be null. bess_and_poc and poc_only require a co-located asset.
Green co-location: where grid import is not permitted, poc_power_mw must be >= 0 — the battery may only charge from the co-located producer. Because bess_only treats the battery's power as the power reaching the grid, a charging bess_only target is rejected on such an asset: at scheduling time there is no way to show the energy came from the producer rather than the grid. To charge, use bess_and_poc with poc_power_mw=0, which pins the point of connection and therefore guarantees producer-only charging.
In bess_and_poc, bess_power_mw may not exceed poc_power_mw: the co-located producer is a source and cannot absorb a surplus.
Targets may span multiple 15-minute delivery blocks. Every block that a target lands in is overridden in full with the submitted targets; blocks not covered by the request are left untouched. At the start of each block the target resets to 0 MW unless a target is placed exactly at the block start.
The entire request is rejected when:
- any target is beyond the end of the next UTC day,
- any target lands in a delivery block starting in less than 6 minutes (gate closure),
- two targets overlap, i.e. fall within the same second,
- any target's magnitude is implausibly large (
abs(power)> 2× the asset's usable power capacity) — a loose sanity check, not an availability check.
Note: Targets within the sanity bound are always accepted, even when they exceed the currently available power. During unavailability or power limits they are not rejected — they are clamped to the power available for the duration of the event.
Multi-product delivery: targets are additionally capped to the power available for wholesale: per direction, the asset's available power (including any power limits) reduced by the reserved FCR capacity scaled by the regulatory buffer and by the reserved aFRR capacity.
The dispatch outcome of each target — executed in full, clipped, rejected, or failed — can be reviewed after the fact via GET /assets/{asset_id}/reports/schedule/dispatches.
Path parameters
- asset_idREQUIRED
- string
Request body · required
- schedulesREQUIRED
- array[object]List of scheduled power events
curl -X PUT "https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/scheduled" \
-H "Authorization: Bearer $EBX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"schedules":[{"control_mode":"passive","power_mw":0,"bess_power_mw":0,"poc_power_mw":0,"pv_power_mw":0,"delivery_time":"2026-09-10T13:30:00Z"}]}'import httpx
r = httpx.put(
"https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/scheduled",
headers={"Authorization": f"Bearer {token}"},
json={
"schedules": [
{
"control_mode": "passive",
"power_mw": 0,
"bess_power_mw": 0,
"poc_power_mw": 0,
"pv_power_mw": 0,
"delivery_time": "2026-09-10T13:30:00Z",
},
],
},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/scheduled", {
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"schedules":[{"control_mode":"passive","power_mw":0,"bess_power_mw":0,"poc_power_mw":0,"pv_power_mw":0,"delivery_time":"2026-09-10T13:30:00Z"}]}),
});
const data = await res.json();{
"schedules": [
{
"control_mode": "passive",
"set_power_mw": 0,
"bess_power_mw": 0,
"poc_power_mw": 0,
"delivery_time": "2026-09-10T13:30:00Z",
"set_time": "2026-09-10T13:30:00Z"
}
],
"warnings": [
"<string>"
]
}- 400malformed request
- 401no valid token
- 403scope missing
- 404unknown or not visible
- 409state conflict
- 422validation failed
- 429rate limited
GET/v1/assets/{asset_id}/forecasts/anticipated_dispatch
Get anticipated dispatch forecast N seconds ahead
dispatch.read
Return the step-interpolated scheduled dispatch power (MW) at a future instant.
Step-interpolation uses the most recent scheduled event with set_time <= target. If no such event exists within the delivery block, the value defaults to 0 MW.
Path parameters
- asset_idREQUIRED
- string
Query parameters
- seconds_into_the_futureREQUIRED
- integerForecast horizon in seconds from now (max 1 day / 86_400 s)
curl "https://api.ebx.energy/v1/assets/DE_BESS_01/forecasts/anticipated_dispatch?seconds_into_the_future=900" \
-H "Authorization: Bearer $EBX_TOKEN"import httpx
r = httpx.get(
"https://api.ebx.energy/v1/assets/DE_BESS_01/forecasts/anticipated_dispatch?seconds_into_the_future=900",
headers={"Authorization": f"Bearer {token}"},
)
print(r.json())const res = await fetch("https://api.ebx.energy/v1/assets/DE_BESS_01/forecasts/anticipated_dispatch?seconds_into_the_future=900", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await res.json();{
"set_time": "2026-09-10T13:30:00Z",
"set_power_mw": 0
}- 401no valid token
- 403scope missing
- 404unknown or not visible
- 422validation failed
- 429rate limited