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.
| Name | What it does |
|---|---|
start + end | ISO window (hour boundary, start included, end excluded) — or window_* / flex_pack / Sparvo slot_id |
mode | cheapest or expensive (optional, default cheapest) |
role | consume or export (optional, default consume) |
count | Number of hour slots (1–168) or use energy / Flex segments below |
energy_kwh + max_power_kw | Alternative: API sets count = ceil(energy_kwh / max_power_kw) |
Optional parameters (common)
| Name | What it does |
|---|---|
timezone | For naive start/end and response times |
min_gap_hours | Minimum hours between any two picked slots |
max_consecutive | Max hours in a row |
max_per_day | Cap slots per calendar day |
allowed_hours | e.g. 8-22 — only those local hours |
weekdays | e.g. mon,tue,wed,thu,fri |
exclude_negative | Skip negative-price hours |
allocation | global (default) or day_first (balance across days) |
Important response fields
| Field | What it means |
|---|---|
decision_status | ok, empty (no match), or not_available |
window.hours_returned | How many slots you actually got |
window.clipped | true if fewer than requested (not enough priced hours) |
price_context | Cheapest, expensive, median, spread in the window |
slots[].start / end | When to turn load on/off |
slots[].price_ct_kwh | Price for that slot — model price, never rewritten; use this for ranking/cost math |
slots[].dump_risk_pct / dump_scenario_ct_kwh | From 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_plan | Slots per date |
baselines | Cost estimates vs random/naive picks |
Common mistakes
startafterend→ HTTP 400invalid_range- Forgetting
countandenergy_kwh/segments→ HTTP 400incomplete_request - Omitting
mode/role→ defaultscheapest/consume(not 400) - Missing ISO
start/endand nowindow_start_hour+window_end_hourand noflex_packand noslot_id(Sparvo merge) → HTTP 400incomplete_request countlarger than tier horizon (168 full / 48 basic) → HTTP 400- Expecting HTTP error when tier is too low — you get
decision_status: not_availablewith HTTP 200
Next steps
- Hours Demand — auto hour count from SOC
- Decision API — hourly traffic light
- Webhooks —
plant.plan_updatedpush - Loxone Flex Windows — loads on/off, Sparvo schedules
- Loxone windows (legacy) — two-block GET/Parse
- Errors — full 400 error list
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.
| Parameter | Meaning |
|---|---|
segments | Comma list, e.g. 7 or 3,5 |
segment_consecutive | off, soft, or required (per segment or one value for all) |
hours_completed | Hours already charged in the active segment |
current_segment_index | Active segment (0-based) |
repeat_daily | Echoed; not applied in v0.4 (wallbox stays one window; Phase 2+) |
deadline_strict | Echoed; after end slots/Demand are always 0 (soft-continue reserved) |
deadline_hour | Sparvo 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_id | Flex 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_hour | Rolling window (0–23, overnight ok) — instead of ISO start/end |
flex_pack | Loxone: 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.
Register for Community or code samples with your personal key.