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
Required scopes: 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
The control payload matches scheduled dispatch: control_mode, bess_power_mw, poc_power_mw, and reserved pv_power_mw, with the same per-mode field matrix. set_power_mw remains a deprecated alias for bess_power_mw.
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-10-22T20:30:00Z",
"end_time": "2026-10-22T20:30:00Z",
"set_power_mw": 0,
"control_mode": "passive",
"bess_power_mw": 0,
"poc_power_mw": 0,
"pv_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
Required scopes: 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. - While an immediate dispatch is active, conflicting wholesale schedule events for the same asset are rejected.
- 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):
- An active
bess_power_mwtarget must not exceed the asset'susable_power_capacityin magnitude. 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. 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 on this basis. end_timeis required and must be in the future.end_timemust not run past the end of the next UTC day. An override lasting longer than that is a plan rather than an override, and plans belong in the schedule, which enforces the same horizon. Refused with 422IMMEDIATE_DISPATCH_TOO_LONG; while the asset is in phase 1 of the validation rollout the dispatch is still created, with the breach returned inwarnings.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 (default 10s). Requests with a shorter effective window are rejected with 400.
The control payload matches scheduled dispatch: control_mode, bess_power_mw, poc_power_mw, and reserved pv_power_mw, with the same per-mode field matrix. set_power_mw remains a deprecated alias for bess_power_mw.
Path parameters
- asset_idREQUIRED
- string
Request body · required
- control_mode
- enum
passivebess_onlybess_and_pocpoc_onlyControl scheme deciding which setpoints apply. Defaults to bess_only when omitted (backwards compatible with single-BESS integrations). passive: no setpoints (free run); bess_only: bess_power_mw; bess_and_poc: both setpoints, PoC target has priority; poc_only: poc_power_mw. bess_and_poc and poc_only require a co-located asset. - power_mw
- numberDeprecated: use bess_power_mw. Implicitly mapped to bess_power_mw when the latter is absent.
- bess_power_mw
- numberBESS power setpoint in MW (charging negative, discharging positive). Required for bess_only and bess_and_poc; must be null/zero otherwise.
- poc_power_mw
- numberPoint-of-connection power setpoint in MW (import negative, export positive). Required for poc_only and bess_and_poc; must be null/zero otherwise.
- pv_power_mw
- numberReserved for a future direct PV setpoint — must be null; PV is currently steered via the PoC target.
- 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. Must not run past the end of the next UTC day — an override lasting longer than that is a plan, and plans belong in the schedule, where they are visible per delivery block and metered against the asset's other commitments. While the asset is in phase 1 of the validation rollout a longer window is still accepted, with the breach returned in
warnings; in phase 2 it is refused. - set_power_mw
- numberDeprecated: use bess_power_mw. Implicitly mapped to bess_power_mw when the latter is absent.
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 '{"control_mode":"passive","power_mw":0,"bess_power_mw":0,"poc_power_mw":0,"pv_power_mw":0,"start_time":"2026-10-22T20:30:00Z","end_time":"2026-10-22T20:30:00Z","set_power_mw":0}'import httpx
r = httpx.post(
"https://api.ebx.energy/v1/assets/DE_BESS_01/dispatch/immediate",
headers={"Authorization": f"Bearer {token}"},
json={
"control_mode": "passive",
"power_mw": 0,
"bess_power_mw": 0,
"poc_power_mw": 0,
"pv_power_mw": 0,
"start_time": "2026-10-22T20:30:00Z",
"end_time": "2026-10-22T20:30:00Z",
"set_power_mw": 0,
},
)
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({"control_mode":"passive","power_mw":0,"bess_power_mw":0,"poc_power_mw":0,"pv_power_mw":0,"start_time":"2026-10-22T20:30:00Z","end_time":"2026-10-22T20:30:00Z","set_power_mw":0}),
});
const data = await res.json();{
"start_time": "2026-10-22T20:30:00Z",
"end_time": "2026-10-22T20:30:00Z",
"set_power_mw": 0,
"control_mode": "passive",
"bess_power_mw": 0,
"poc_power_mw": 0,
"pv_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
Required scopes: dispatch.write
Disable the active immediate dispatch for a specific asset.
Sets the active dispatch's end_time to the current time 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-10-22T20:30:00Z",
"end_time": "2026-10-22T20:30:00Z",
"set_power_mw": 0,
"control_mode": "passive",
"bess_power_mw": 0,
"poc_power_mw": 0,
"pv_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
Required scopes: 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.
Rows describe stored schedule targets, including system-created defaults. created_by and updated_by retain provenance. dispatch_applicability distinguishes applicable targets, defaults suppressed in Testing, and historical defaults whose lifecycle at delivery cannot be established. A missing row is not a zero target. A scheduled zero does not confirm that the device received or applied zero. Targets do not extend past their delivery block merely because the next block has no row.
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",
"dispatch_applicability": "applicable",
"created_by": "<string>",
"updated_by": "<string>",
"set_power_mw": 0,
"bess_power_mw": 0,
"poc_power_mw": 0,
"delivery_time": "2026-10-22T20:30:00Z",
"set_time": "2026-10-22T20:30:00Z"
}
],
"warnings": [
"<string>"
],
"dry_run": false
}- 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
Required scopes: 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.
Grid constraints: on assets where grid import is forbidden, explicit PoC targets (bess_and_poc or poc_only) must be >= 0. Explicit PoC targets must also respect the grid-connection limit. For standalone batteries these grid checks apply to bess_only targets too, because battery power crosses the grid connection.
Co-located bess_only: the BESS follows its target while the producer exports its surplus freely, subject to the site's import/export limits. Negative BESS targets are accepted for PV charging even when grid import is forbidden. The EMS must reduce charging when production is insufficient and enforce the export ceiling; acceptance does not guarantee that the full requested power will be available.
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 block that has started, or whose conditional aFRR gate has closed, inclusive at 5 minutes 15 seconds before block start,
- 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.
The conditional gate applies when the target block has an aFRR energy bid that is successful, in FALLBACK, or unresolved, or the asset is currently commanded active in aFRR (including internal collateralization). Capacity bids alone do not close it. The gate can reopen when neither condition remains. Unavailable activity data returns 503 when needed to decide. When neither aFRR condition applies, updates are accepted until block start. Allow roughly 10 seconds for an accepted update to take effect; this is an estimate, not a guaranteed delivery time or an additional gate. Updates within that final window are accepted and propagated on a best-effort basis: acceptance confirms storage, not execution, and the update may not be applied during that block.
Note: capacity validation uses declared data only — configured capacity, the limits and unavailabilities on record, and what other products have committed for the block. Live asset state also determines the conditional wholesale gate; capacity checks do not use it. A target within the configured capacity is accepted even if the asset cannot presently deliver it. Which breaches are refused and which come back in warnings depends on the asset's strict_validation flag (or the strict_validation query parameter, when given), and on which check fails first; the validation reference is the authority on both for these checks.
Stored targets are not reduced against reserved market capacity at execution time. This does not guarantee that the executed magnitude always equals the requested one.
The dispatch outcome of each target can be reviewed after the fact via GET /assets/{asset_id}/reports/schedule/dispatches. A row there is labelled clipped when the executed magnitude was smaller than the one requested, whatever the cause.
Path parameters
- asset_idREQUIRED
- string
Query parameters
- validate_only
- booleandefault falseRun every check and store nothing. The response is the one this request would really have produced — its status code included, so a payload that would be refused is still refused — with
dry_run: true. Worth using before a live PUT: a real PUT replaces every target in each block it touches, so there is otherwise no way to validate a schedule without overwriting the one you are asking about. - strict_validation
- booleanOverride the asset's
strict_validationflag for this request.falsestores a schedule that breaks a plausibility rule (for examplePHYSICAL_CAPACITY_EXCEEDED) and returns the breach inwarningsinstead of a 409. Gate closure, the horizon and the 2x capacity sanity bound are still enforced. Omit to use the asset's flag.
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-10-22T20: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-10-22T20: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-10-22T20:30:00Z"}]}),
});
const data = await res.json();{
"schedules": [
{
"control_mode": "passive",
"dispatch_applicability": "applicable",
"created_by": "<string>",
"updated_by": "<string>",
"set_power_mw": 0,
"bess_power_mw": 0,
"poc_power_mw": 0,
"delivery_time": "2026-10-22T20:30:00Z",
"set_time": "2026-10-22T20:30:00Z"
}
],
"warnings": [
"<string>"
],
"dry_run": false
}- 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
Required scopes: dispatch.read
Return scheduled dispatch power in MW; missing targets and testing defaults are null.
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-10-22T20:30:00Z",
"set_power_mw": 0
}- 401no valid token
- 403scope missing
- 404unknown or not visible
- 422validation failed
- 429rate limited