Knowledge hub
9 minValidation of bids & dispatches
How the EBX API checks bids and dispatch requests against the asset's declared reality, and what the response tells you.
In short
Every write to the EBX API (a wholesale schedule, an immediate dispatch, an FCR bid, an aFRR capacity or energy bid) is checked against what EBX knows about the asset: its configured properties, the restrictions you have declared, and the obligations already committed for the same delivery window.
Checks run per 15-minute delivery block and per direction. Discharge and export are positive; charge and import are negative. A request is stored completely or not at all, and a rejection names every delivery window that caused it.
Who this is for
Optimisers integrating against the EBX v1 API who need to know which requests will be accepted, which will be refused, and what the response contains.
How validation works
What EBX validates against
Validation uses declared data, not live telemetry, so that the result for a future delivery window is deterministic and reproducible:
- Asset properties: usable power and energy capacity, grid connection power, whether grid import is allowed, and the supported control modes.
- Prequalified capacity: the approved FCR power and the approved aFRR power per direction. A value of
0.0is a real ceiling, not an unset field. - Enablement:
fcr_enabled,afrr_enabled,wholesale_enabled,control_enabled. - Restrictions you declared: unavailability, power limits (MW, signed) and energy limits (MWh), submitted through the restriction endpoints.
- Committed obligations: FCR and aFRR bids and awards, aFRR energy bids, the wholesale schedule, and an active immediate dispatch.
Live asset state (available charge and discharge power, state of charge) is used for execution, not for validating future windows.
Rules that apply to every write path
- First committed wins. A request is validated against everything committed before it. Nothing already committed is displaced by a later request. Because ancillary services outrank wholesale, a wholesale schedule has to fit around bids and awards. That is enforced at submission once an asset has
strict_validationenabled. Until then a conflict comes back as a warning, the schedule is accepted, and the target is clipped during execution. - Pending results count as committed. An offered bid counts at its offered power until a result is known. Once the result arrives, the award replaces the offer. For a partial clearance, only the cleared power stays committed. Capacity is released only when a bid is rejected or fails.
- Committed statuses are
PENDING,SUBMITTED,SUCCESSFULandFALLBACK.FALLBACK(the auction did not clear and the TSO replacement price applies) is a terminal state that still counts against your ceilings. - Restrictions are conservative at the edges. A restriction that touches any part of a delivery window applies to the whole window, because an award binds its entire block. An open-ended restriction covers everything after its start.
- Multiple bids per block are allowed. You may place several bids for the same block and direction at different prices; ceilings apply to their sum.
- Timestamps are ISO 8601 with an explicit UTC offset. Bid intervals and query windows are aligned to 15 minutes, and all windows are half-open.
- Idempotency. A duplicate
idempotency_keyfor the same interval within one request is rejected with 422. For aFRR the scope is the same interval and product. Reusing one key across different intervals or products is allowed. A retry with a known key returns the original result.
Market windows and product shapes
| Write path | Delivery unit | Market gate (Europe/Berlin) | EBX recommended gate |
|---|---|---|---|
| FCR capacity bids | 4-hour block, symmetric (NEGPOS) | D-1 08:00 | 07:30 |
| aFRR capacity bids | 4-hour block, directional (POS / NEG) | D-1 09:00 | 08:30 |
| aFRR energy bids | One 15-minute MTU | 25 min before the MTU | 30 min before |
| Scheduled dispatch | 15-minute block | 6 min before the block starts | n/a |
| Immediate dispatch | From now until end_time | n/a | n/a |
The market gate is the enforced gate: after it, the request is refused. Between the EBX recommended gate and the market gate, changes are accepted on a best-effort basis, and EBX cannot guarantee that they are forwarded to the market or resynchronised.
Capacity blocks are 4 hours in Berlin local time, which means 3 or 5 UTC hours on the two clock-change days. Energy products are single 15-minute MTUs, so a day has 92, 96 or 100 of them. The open tender blocks for a delivery day are listed by GET /v1/assets/{id}/markets/{product}/bids/intervals; a bid interval that does not match one of them is refused.
Bid power is an integer number of MW, at least 1 MW. Capacity prices are at least 0 with at most two decimals. Energy prices carry at most two decimals and may be negative, and the sign sets the payment direction towards the market.
What is checked on each write path
Scheduled dispatch
PUT /v1/assets/{id}/dispatch/scheduled carries per-second bess_power_mw and poc_power_mw targets.
Each 15-minute block your request touches is replaced in full. A block resets to 0 MW at its start unless a target is placed there, so a partial resubmission clears what it does not restate.
Refused at submission:
- The asset does not have
wholesale_enabled = true. - A target falls in a block that starts in fewer than 6 minutes, or beyond the end of the next UTC day.
- The schedule is empty, two targets share the same second, or
pv_power_mwis set. - The
control_modedoes not match the power fields supplied, or a point-of-connection mode is used on an asset without a controllable point of connection. - The magnitude of
poc_power_mwexceedsgrid_connection_power_mw; import is requested whilegrid_import_allowed = false; or, inbess_and_poc, the battery target exceeds the point-of-connection target.
Immediate dispatch
POST /v1/assets/{id}/dispatch/immediate sets one set_power_mw from now until end_time.
Immediate dispatch is a break-glass override. It is deliberately not gated by wholesale_enabled, operational status or control flags, and it deliberately bypasses restrictions, market commitments and live availability. EBX applies it best effort. Market obligations are not checked at submission, and the execution-time protection intended for them is not implemented today, so an immediate dispatch can currently displace an FCR or aFRR obligation.
Refused at submission:
start_time, if supplied, is not the current moment (1 second tolerance);end_timeis not in the future or not after the start; the window is shorter than the minimum duration.- A separate immediate dispatch already covers part of the requested window: one scheduled to start later, for example. A dispatch that is merely active is not a refusal: posting a new one supersedes it automatically, and the old one is truncated at the handoff so its history is kept.
For anything other than an emergency intervention, use the scheduled dispatch endpoint.
FCR and aFRR bids
POST / PUT / DELETE on /v1/assets/{id}/markets/fcr/bids, /markets/afrr/capacity/bids and /markets/afrr/energy/bids.
Refused at submission:
- The asset is not enabled for the market (
fcr_enabled,afrr_enabled). - The gate for the delivery block has passed.
- The interval is not an open tender block for that product.
offered_power_mwis not an integer of at least 1 MW, or a price breaks the format rules above.- A price sits outside the market’s legal range: a capacity price above 15,000 EUR/MW, or a balancing-energy price outside −15,000 to 15,000 EUR/MWh. A capacity bid is checked on both its own price and the energy price it would activate at, so an impossible energy price cannot enter through the capacity endpoint.
- The committed power for the asset exceeds its prequalified capacity: total FCR power for FCR, and per-direction aFRR power for aFRR. Today capacity and energy bids are metered against the aFRR prequalification independently of each other.
- The bid is no longer editable:
start_timeandend_timeare immutable once a bid has been submitted to the market, and onlyPENDINGandSUBMITTEDbids can be changed or deleted. Power and price stay editable until the gate. - A
REPLACEbatch does not carry areplace_scopeand every bid inside it. The batch is stored as a whole or refused as a whole.
FCR bids carry no direction: the same power is reserved for charging and discharging. aFRR bids are directional (pos is discharge into the grid, neg is charge from the grid), and the direction cannot change after submission. An aFRR capacity bid carries a linked energy price, because an award creates a mandatory energy bid at that price.
An edit that arrives after the bid was submitted to the market is queued for resynchronisation and may end as FAILED_LATE_EDIT.
The price limits are the market’s own, not ours, so they are configuration rather than fixed schema bounds and can change without a release — the figures above are today’s defaults. A negative balancing-energy price is deliberately legal, because paying to absorb energy is a real offer; that is why only that field has a floor at all. Every price in the request that breaks its bound is reported together, so you are not fixing them one rejection at a time.
Power and energy EBX reserves for your obligations
Balancing obligations need headroom beyond their nominal power. EBX reserves it at execution, and checks it at submission once an asset has <code>strict_validation</code> enabled:
| Reservation | Amount |
|---|---|
| FCR power, both directions | 1.25 × contracted FCR power |
| FCR energy, each direction | ⅓ MWh per MW (20 minutes at full power) |
| aFRR capacity energy, awarded direction | 1 MWh per MW (60 minutes) |
| aFRR energy, per 15-minute MTU | 0.25 MWh per MW |
| Energy allocation order | FCR, then aFRR capacity, then aFRR energy |
Energy reservations are not checked when a bid is placed. Available energy changes over the 24 hours between a day-ahead bid and delivery, and recharge management is the optimiser's responsibility.
At execution, ancillary obligations are served first and the wholesale target is limited to what remains: the dispatcher gives wholesale the power left after 1.25 × the FCR reservation and the aFRR reservation for that block. Where that happens, the wholesale dispatch is reported as clipped in the dispatch report. Once an asset has strict_validation enabled, the same arithmetic runs at submission, so a request that would breach the reservation is refused up front instead of surfacing as a clip after the fact. Until the flag is on it comes back as a warning and the request is still accepted.
Worked example
A 10 MW / 20 MWh asset, delivery block 12:00 to 16:00.
- FCR is awarded 4 MW. That reserves 5 MW in each direction (1.25 × 4) and 1.33 MWh on each side.
- aFRR capacity
posis awarded 3 MW. The discharge reservation is now 8 MW, with a 3 MWh discharge buffer on top. - 2 MW of discharge power is left for wholesale in that block. A +5 MW wholesale target is 3 MW beyond the asset's discharge capability and can only be delivered at 2 MW.
- On the charge side, 5 MW remains after the 5 MW FCR reservation, with no aFRR
negaward to consume it.
Restrictions you declare
Unavailability, power limits and energy limits are declared through /v1/assets/{id}/unavailability, /power_limits and /energy_limits.
- A restriction is always accepted, even when it conflicts with an obligation already committed. Physical reality wins, and EBX needs to know about it in order to arrange backup for the affected obligation. The obligation itself is not cancelled by the restriction, and the contractual terms for that shortfall apply.
- Shortening or deleting a restriction is always allowed, since it only adds capability.
- Geometry:
start_timecannot be in the past, the window is at least 15 minutes, windows of the same type may not overlap,PATCHcan only shorten a window, andDELETEworks only on a window that has not started. A power limit whose upper and lower bound are both zero (within 0.5 kW) means the asset is unavailable. - Bounds have to be physically possible: power-limit bounds stay within plus or minus
usable_power_capacity_mw, and energy-limit bounds between 0 andusable_energy_capacity_mwh. If a bound is omitted, the asset's capacity must be configured so that the open side can be resolved.
DSO curtailment is not part of validation. It is applied at the asset by the EMS and shows up in the asset's live available power.
Error responses
INVALID_INTERVAL, which needs the open tender blocks.Rejections carry a machine-readable error code. The codes in use today are:
- GATE_CLOSED
- the gate for that delivery block has passed. The body carries
gate_time. - MARKET_DISABLED
- the asset is not enabled for that market.
- SETPOINT_OUT_OF_RANGE
- a power value is outside the range the asset can accept.
- PRICE_OUT_OF_RANGE
- a bid price is outside the market’s configured range. Every offending price is listed, each with its
field_name, thevalueyou sent, and theminimumandmaximumin force. Nothing was stored. - IMMEDIATE_DISPATCH_OVERLAP
- another immediate dispatch overlaps the window. An active dispatch does not raise this, because it is superseded automatically.
- FCR_PREQUALIFIED_CAPACITY_EXCEEDED / AFRR_PREQUALIFIED_CAPACITY_EXCEEDED
- committed power would exceed the asset's prequalified capacity.
- INVALID_INTERVAL
- the bid interval is not an open tender block. The body carries
suggested_intervals.
A rejection that is specific to delivery windows lists them, with the committed value, the ceiling it breaches and the amount over. Adjacent violating blocks are merged into one window, so a 4-hour block whose sixteen quarter-hours all breach is reported once:
{
"detail": {
"error": "AFRR_PREQUALIFIED_CAPACITY_EXCEEDED",
"message": "Committed aFRR capacity-market power would exceed the asset's prequalified capacity in 1 delivery window(s). No bids were stored.",
"market": "capacity",
"violations": [
{
"start_time": "2026-09-01T12:00:00Z",
"end_time": "2026-09-01T16:00:00Z",
"product": "pos",
"committed_power_mw": 5.0,
"prequalified_capacity_mw": 4.0,
"exceeded_by_mw": 1.0
}
]
}
}Every error body is wrapped in detail. committed_power_mw is what the asset would hold in that window with your request applied, not your request's own contribution, and prequalified_capacity_mw is the ceiling it breaches, so the comparison reads as "this is what you would be holding, and this is all you may hold". An aFRR body also carries the market the bids were for, and each violation the product; an FCR body carries neither, because FCR has one symmetric ceiling and no direction.
Checks run cheapest first (structure, then enablement, then the gate, then ceilings), but all violations of the failing check are reported, not only the first one.