Decision Windows API

GET /v1/decision/windows — N cheapest hours / Flex segments. Loxone wallbox: Flex Windows. Open API; Community 120 requests/day.

Decision Windows answers: “Which N hours in this date range are cheapest (or most expensive)?”

Everyday example

Your wallbox needs 6 charging hours in the next 3 days. You call the API with count=6, mode=cheapest, and role=consume. It returns six one-hour slots like “Tue 02:00–03:00 at 2,1 ct/kWh.” Your EMS turns charging on only in those hours.

When to use / when not

Use when:

  • You already know how many hours you need (or kWh ÷ kW power)
  • You want spacing rules (min_gap_hours, max_consecutive, allowed_hours)

Do not use when:

  • You want the API to compute hours from battery SOC → Hours Demand
  • A simple hourly traffic light is enough → Decision API

What you need

  • Community tier or higher
  • Header: X-API-Key: sf_live_YOUR_KEY
  • Start and end times for your search window (ISO 8601)

Step by step

1. Choose a time window: start (included) and end (excluded), e.g. midnight to midnight in three days. 2. Set mode=cheapest for charging/consuming, or mode=expensive for export/discharge. 3. Set role=consume or role=export (labels for your docs/UI). 4. Set count (number of hours) or energy_kwh + max_power_kw (API computes count). 5. Add optional filters: min_gap_hours, max_per_day, allowed_hours=8-22, etc. 6. Read slots[] for relay times; read day_plan for hours per calendar day.

Minimal request and response


curl "https://api.spotpriceapi.com/v1/decision/windows?count=10&start=2026-08-02T00:00:00&end=2026-08-05T00:00:00&mode=cheapest&role=consume&timezone=Europe/Vienna&min_gap_hours=2" \
  -H "X-API-Key: sf_live_YOUR_KEY"

{
  "decision_status": "ok",
  "decision_engine": "windows_v1",
  "allocation": "global",
  "window": { "hours_available": 72, "hours_requested": 10, "hours_returned": 10 },
  "slots": [
    { "rank": 1, "start": "2026-08-03T02:00:00+02:00", "end": "2026-08-03T03:00:00+02:00", "price_ct_kwh": 2.1, "action": "favor" }
  ],
  "day_plan": { "2026-08-02": 2, "2026-08-04": 8 }
}

In plain words: You asked for 10 hours; you got 10. rank: 1 is the best (cheapest) pick. day_plan shows how those hours spread across days.

Endpoint


GET https://api.spotpriceapi.com/v1/decision/windows

Parameters

Need a window and a count (or Flex merge). mode / role default to cheapest / consume if omitted.

NameWhat it does
start + endISO window (hour boundary, start included, end excluded) — or window_* / flex_pack / Sparvo slot_id
modecheapest or expensive (optional, default cheapest)
roleconsume or export (optional, default consume)
countNumber of hour slots (1–168) or use energy / Flex segments below
energy_kwh + max_power_kwAlternative: API sets count = ceil(energy_kwh / max_power_kw)

Optional parameters (common)

NameWhat it does
timezoneFor naive start/end and response times
min_gap_hoursMinimum hours between any two picked slots
max_consecutiveMax hours in a row
max_per_dayCap slots per calendar day
allowed_hourse.g. 8-22 — only those local hours
weekdayse.g. mon,tue,wed,thu,fri
exclude_negativeSkip negative-price hours
allocationglobal (default) or day_first (balance across days)

Important response fields

FieldWhat it means
decision_statusok, empty (no match), or not_available
window.hours_returnedHow many slots you actually got
window.clippedtrue if fewer than requested (not enough priced hours)
price_contextCheapest, expensive, median, spread in the window
slots[].start / endWhen to turn load on/off
slots[].price_ct_kwhPrice for that slot — model price, never rewritten; use this for ranking/cost math
slots[].dump_risk_pct / dump_scenario_ct_kwhFrom Forecast hour: pct 0…100; scenario = dump when pct > 0, else the same point price as price_ct_kwh (always usable). Additive only — ranking/selection always uses price_ct_kwh
day_planSlots per date
baselinesCost estimates vs random/naive picks

Common mistakes

  • start after end → HTTP 400 invalid_range
  • Forgetting count and energy_kwh/segments → HTTP 400 incomplete_request
  • Omitting mode / role → defaults cheapest / consume (not 400)
  • Missing ISO start/end and no window_start_hour+window_end_hour and no flex_pack and no slot_id (Sparvo merge) → HTTP 400 incomplete_request
  • count larger than tier horizon (168 full / 48 basic) → HTTP 400
  • Expecting HTTP error when tier is too low — you get decision_status: not_available with HTTP 200

Next steps

Flex v1.1 (loads on/off) — optional

count picks N cheap hours in a window — not the wallbox. The wallbox (and relays/pool) is a Flex slot: Sparvo device + schedule (API field recipes), PicoC slot in Portal EMS connection, PicoC on/off. Without flex parameters the response stays windows_v1.

ParameterMeaning
segmentsComma list, e.g. 7 or 3,5
segment_consecutiveoff, soft, or required (per segment or one value for all)
hours_completedHours already charged in the active segment
current_segment_indexActive segment (0-based)
repeat_dailyEchoed; not applied in v0.4 (wallbox stays one window; Phase 2+)
deadline_strictEchoed; after end slots/Demand are always 0 (soft-continue reserved)
deadline_hourSparvo latest start 0–23 (inclusive) — not window end. Without window_* the API infers clock hours from ISO start/end (18:00→06:00 stays overnight-safe). A deadline outside the window is ignored (window stays).
slot_idFlex slot (1 / slot_3 / wallbox_1) — Sparvo merge when flex_pack is omitted. One PicoC output 1–8 per plant
window_start_hour / window_end_hourRolling window (0–23, overnight ok) — instead of ISO start/end
flex_packLoxone: packed I4/I5/I6/hours_completed for one <v.0>

With flex params: decision_engine: windows_v1.1, ems block directly after decision_status. ems.current_segment_hours / hours_needed_today = remaining need (0 when segment done). window.hours_requested is the same remaining need. ems.next_slot_start = next hour from the current full hour. deadline_reached is true whenever now ≥ end (even without deadline_strict).

GET (curl/scripts) — query params as before. POST (Loxone VO) — like Hours one analog: {"slot_id":"3","flex_pack":<v.0>}. Type slot_id. flex_pack=0 means omitted (Sparvo merge); a progress/plug-only pack (segments=0) still merges. mode/role default cheapest/consume. No named <v.Vi…> tags, no five <v.n> as five wires.


curl -X POST "https://api.spotpriceapi.com/v1/decision/windows" \
  -H "X-API-Key: sf_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"window_start_hour\":18,\"window_end_hour\":6,\"segments\":7,\"segment_consecutive\":\"soft\",\"mode\":\"cheapest\",\"role\":\"consume\",\"timezone\":\"Europe/Vienna\",\"hours_completed\":2,\"slot_id\":\"wallbox_1\"}"

curl "https://api.spotpriceapi.com/v1/decision/windows?window_start_hour=18&window_end_hour=6&segments=7&segment_consecutive=soft&mode=cheapest&role=consume&timezone=Europe/Vienna&hours_completed=2" \
  -H "X-API-Key: sf_live_YOUR_KEY"

Loxone walkthrough: Flex Windows.

Device schedules (Sparvo → EMS)


GET https://api.spotpriceapi.com/v1/plant/device-schedules

Returns EMS-linked devices (api_linked): schedules from Sparvo, PicoC slot via Portal EMS connection (/settings/ems). Fields include slot_id, recipes[] (UI: Schedule), matching recipe, segments, window_*. Community key.

Start with SpotpriceAPI

Register for Community or code samples with your personal key.

Live API status