EBX

Knowledge hub

9 min

Validation 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.0 is 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_validation enabled. 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, SUCCESSFUL and FALLBACK. 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_key for 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 pathDelivery unitMarket gate (Europe/Berlin)EBX recommended gate
FCR capacity bids4-hour block, symmetric (NEGPOS)D-1 08:0007:30
aFRR capacity bids4-hour block, directional (POS / NEG)D-1 09:0008:30
aFRR energy bidsOne 15-minute MTU25 min before the MTU30 min before
Scheduled dispatch15-minute block6 min before the block startsn/a
Immediate dispatchFrom now until end_timen/an/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_mw is set.
  • The control_mode does 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_mw exceeds grid_connection_power_mw; import is requested while grid_import_allowed = false; or, in bess_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_time is 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_mw is 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_time and end_time are immutable once a bid has been submitted to the market, and only PENDING and SUBMITTED bids can be changed or deleted. Power and price stay editable until the gate.
  • A REPLACE batch does not carry a replace_scope and 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:

ReservationAmount
FCR power, both directions1.25 × contracted FCR power
FCR energy, each direction⅓ MWh per MW (20 minutes at full power)
aFRR capacity energy, awarded direction1 MWh per MW (60 minutes)
aFRR energy, per 15-minute MTU0.25 MWh per MW
Energy allocation orderFCR, 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 pos is 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 neg award 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_time cannot be in the past, the window is at least 15 minutes, windows of the same type may not overlap, PATCH can only shorten a window, and DELETE works 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 and usable_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

422The request is structurally invalid. Usually fixable without looking at the asset; the exception is INVALID_INTERVAL, which needs the open tender blocks.
404The asset does not exist for this caller.
409The request conflicts with the asset's state, its restrictions, or existing commitments.

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, the value you sent, and the minimum and maximum in 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:

Rejection409
{
  "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.

Send feedback

This goes straight to the docs team on Slack, with the page you are on. It is not attributed to you, so say who you are if you would like an answer.

0/2000