Record what a site pays for power

Describe a location's electricity contract once, see its real price for every 15 minutes, and let Flowion charge in the cheapest hours.

4 min readEdit this page
On this page

A supply contract is what a location pays its electricity supplier: the spot price, the supplier's markup, grid fees by time of day, taxes and VAT. Describe it once, and Flowion turns it into the location's real price for every 15 minutes ahead, which is what it uses to move charging into the cheapest hours.

A supply contract is about the power coming in. What drivers pay you is a separate thing, a tariff, and the two never mix.

How a contract adds up

A contract is a list of one to 16 layers. For every 15-minute slot, Flowion adds the layers together, then applies VAT to the sum.

kindAddsFields
fixedThe same price per kWh, all the timeperKwh
timeOfUseA price per kWh inside the windows you list, nothing outside themwindows: days, start, end, perKwh
spotIndexedThe day-ahead price times multiplier, plus markupPerKwhmultiplier, markupPerKwh, optional zone

A few rules make every contract unambiguous:

  • Amounts are decimal strings in whole currency units: "0.45", never cents or öre, never a JSON number. A negative amount is a rebate.

  • Time-of-use windows are local time, read in the contract's timeZone. start is included and end is not, and a window that crosses midnight is written as two windows. Windows in one layer may not overlap.

  • A spot layer uses the location's own price zone unless you name another zone. The spot price arrives per MWh and is converted to per kWh for you.

  • Write a contract with a spot layer in the market's currency, the currency its day-ahead prices carry. A location contract in another currency is refused, and a project default that reaches such a location shows currencyMismatch in its price curve.

Set a default for the project

PUT/v1/projects/$PROJECT/supply-contractbash
curl -X PUT "$API/projects/$PROJECT/supply-contract" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "EUR",
    "timeZone": "Europe/Stockholm",
    "vatPercent": "25",
    "layers": [
      {"kind": "spotIndexed",
       "multiplier": "1", "markupPerKwh": "0.005"},
      {"kind": "timeOfUse", "windows": [
        {"days": ["Monday", "Tuesday", "Wednesday",
                  "Thursday", "Friday"],
         "start": "06:00", "end": "22:00", "perKwh": "0.05"}]},
      {"kind": "fixed", "perKwh": "0.04"}
    ]
  }'

This one reads: the spot price plus a small supplier markup, a weekday daytime grid fee, a flat energy tax, and 25% VAT on top. The answer is the contract now in force. A field that fails validation is 400, and the message names it. Setting or removing a contract needs the pricing.supply_contract.manage permission.

Every location in the project without a contract of its own uses this default.

Give one location its own contract

PUT/v1/locations/$LOCATION/supply-contractbash
curl -X PUT "$API/locations/$LOCATION/supply-contract" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d @contract.json

The body is the same shape. A location's own contract replaces the project default entirely: the two are never merged layer by layer.

  • GET /v1/locations/{id}/supply-contract reads the location's own contract, and answers 204 when it has none, even if the project has a default.

  • DELETE removes it, and the project default applies again.

Saving replaces the contract in one step, so a location is never without one in between.

See a location's price, slot by slot

GET/v1/locations/$LOCATION/energy/price-curvebash
curl "$API/locations/$LOCATION/energy/price-curve" \
  -H "Authorization: Bearer $TOKEN"
200Response
{
  "outcome": "ok",
  "currency": "EUR",
  "unit": "perKwh",
  "slotMinutes": 15,
  "knownUntil": "2026-10-02T22:00:00Z",
  "segments": [
    {
      "start": "2026-10-01T08:00:00Z",
      "end": "2026-10-01T09:00:00Z",
      "known": true,
      "importPrice": "0.21"
    }
  ],
  "clock": {
    "timeZone": "Europe/Stockholm",
    "offsets": [
      { "from": "2026-10-01T08:00:00Z", "offsetMinutes": 120 }
    ]
  }
}

This is the price the location pays, with the contract's VAT applied, exactly as Flowion plans with it. Each segment is a run of 15-minute slots that share one price. The window is the next 24 hours by default; set from and to for another, up to 31 days.

Read outcome before you draw anything:

outcomeMeans
okThe curve is in segments
noContractNeither the location nor the project has a contract
currencyMismatchThe contract's currency differs from its spot prices'
zoneUnknownA spot layer has no zone to read: give the location a price zone
noForecastThe zone has no prices for this window yet

A slot with known: false is one whose spot price is not published yet. It has no importPrice, and knownUntil marks the first such slot.

Try a contract before you save it

Send a draft to the preview endpoint and get the curve it would produce, with nothing stored:

POST/v1/locations/$LOCATION/energy/price-curve/previewbash
curl -X POST "$API/locations/$LOCATION/energy/price-curve/preview" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d @contract.json

It takes the same from and to, and answers a draft that would not save with the same 400.

Charge in the cheapest hours

With a contract in place, give a session a goal: the energy it should have and when it must be done.

PUT/v1/transactions/$TRANSACTION/deadlinebash
curl -X PUT "$API/transactions/$TRANSACTION/deadline" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chargeBy": "2026-10-02T06:00:00+02:00",
       "energyKwh": 40}'

Flowion then plans the session against the location's price curve. It pauses or slows the session through expensive slots and charges through the cheap ones, finishing by chargeBy. energyKwh is the session's total at the meter, counting what it has already delivered, not what is still needed. Setting a goal needs the session.deadline.manage permission.

A few things to plan for:

  • The site's limits come first. A plan only ever lowers what a session may draw within the electrical limits you set.

  • Sessions do not flicker. A session switches between paused and charging at most once every 10 minutes, because some cars end a session when their charge is cut and restored repeatedly.

  • No price, no plan. A session without a goal, or at a location whose curve is not ok, charges as it would have anyway.

GET /v1/projects/{projectId}/planned-schedule shows the plan for every session that has one.

Endpoints

Energy · Charging priority

In the API reference