API
Allt är statiska JSON-filer som genereras vid varje körning. Ingen nyckel, ingen rate limit, inga anrop mot en server som kan gå ner. Hämta dem med vad du vill — CORS är påslaget av GitHub Pages, och Home Assistant behöver det inte.
Status för senaste körningen
Läser in status…
Endpoints
Bas-URL är <sajtens adress>/api/v1/.
| Sökväg | Innehåll |
|---|---|
status.json | Körstatus per datakälla, degraded, nästa planerade körning |
models.json | Installerade modeller: id, namn, beskrivning, om de ger kvantiler |
zones.json | Alla fyra elområden med aktuellt pris och länkar |
accuracy.json | Träffsäkerhet per elområde, modell och horisont |
longterm.json | Månadens snittpris 1–3 månader fram per elområde: alla modeller med intervall, terminsmarknadens pris, drivkrafter och backtest |
bess.json | Batterikalkyl & BESS: 6 kommersiella offerter, 15-åriga kassaflöden under 3 scenarier, 11-årigt historiskt backtest och elområdesstatistik (SE1–SE4) |
zones/{SE1..SE4}/forecast.json | Timserie från i går 00:00 till +7 dygn, alla modeller, drivkrafter |
zones/{SE1..SE4}/history.json | 30 dygn utfall plus prognosen vi gav 24, 48 … 168 h innan |
zones/{SE1..SE4}/accuracy.json | Träffsäkerhet för ett elområde |
Enheter och konventioner
- Alla priser är i EUR/MWh, avrundade till tre decimaler. För öre/kWh:
multiplicera med växelkursen EUR/SEK och dela med 10. Kursen följer med i fältet
fxi varje fil. Att bara dela med 10 ger eurocent, inte öre. - Vilken modell sajten visar står i
default_model. Välj modell därifrån i stället för att skriva in ett namn — standardmodellen byts när en annan mätt sig bättre. - Tidsstämplar är ISO-8601 med offset i
Europe/Stockholm, och avser leveranstimmens början. source: "official"betyder publicerat day-ahead-pris."forecast"är vår modell."demo"betyder att instansen kör utan ENTSO-E-nyckel och visar syntetiska siffror — använd dem inte till något.resolutionärPT60Mi v1. Fältet finns för att kvartsvärden ska kunna läggas till senare utan att bryta klienter.- Fälten är stabila. Nya fält kan tillkomma; befintliga tas inte bort inom v1.
Uppdatering och cachning
- Filerna skrivs om vid varje körning: fyra gånger per dygn, 06:30, 10:15, 13:30 och
18:00 svensk tid, plus när koden ändras.
generated_atochrun_idsäger vilken körning en fil kommer från, ochstatus.json→next_expected_update_utcnär nästa är planerad. En körning kan starta upp till en halvtimme efter den tiden, ibland mer. - Morgondagens officiella priser publiceras av börsen runt 12:45. Den första körningen därefter, normalt 13:30, har dem.
- GitHub Pages cachar filerna i upp till tio minuter. Att hämta oftare än var femtonde minut ger inget nytt; var trettionde räcker.
Stabilitet
- Inom
v1tas inga fält bort och inget fält byter betydelse. Nya fält kan tillkomma — ignorera de du inte känner igen. En ändring som bryter klienter kommer som/api/v2/. - Nya modeller kan tillkomma och standardmodellen kan bytas. Läs alltid
default_modeli stället för att skriva in ett modell-id.
Saknade värden och fel
actual: null— inget officiellt pris ännu för timmen.- En modell kan sakna värde för vissa timmar. Kontrollera att nyckeln finns i
modelsinnan du läser den. degraded: true— en väsentlig källa svarade inte, och delar av filen kan komma från en tidigare körning. Orsaken står istatus.json→sources.<källa>.error.demo: true— syntetiska priser, instansen saknar ENTSO-E-nyckel. Använd dem inte.fx.stale: true— dagens ECB-kurs kunde inte hämtas och en äldre används.
Fältreferens
Tider är ISO-8601 med offset i Europe/Stockholm om inget annat står.
Priser är EUR/MWh.
zones/{SE1..SE4}/forecast.json — prognosen
| Fält | Typ | Innehåll |
|---|---|---|
zone, zone_name | text | Elområde, t.ex. SE3 och Stockholm. |
generated_at, run_id | tid, text | Körningen som skrev filen. |
unit, resolution | text | EUR/MWh och PT60M (en punkt per timme). |
degraded, demo | bool | Se Saknade värden och fel. |
default_model | text | Modellen sajten visar. |
models | lista | Alla modell-id:n som kan förekomma i serien. |
fx | objekt | pair, rate (SEK per EUR), date, source, fetched_at, stale. |
series[] | lista | En post per timme från i går 00:00 till sju dygn fram, cirka 200 timmar. |
ts | tid | Leveranstimmens början. |
actual | tal | null | Officiellt pris, eller null om det inte är publicerat. |
source | text | official, forecast eller demo. |
models.<id> | objekt | p10, p50, p90. p50 är prognosen; åtta utfall av tio ska hamna mellan p10 och p90. |
drivers | objekt | Förklaringen på förstasidan: |
regime, regime_label_sv | text | Läget som kod och på svenska, t.ex. outage_tight / Bortfall i systemet. |
headline_sv, bullets_sv[] | text | Rubrik och punkter, färdiga att visa. |
outages.items[] | lista | Pågående och kommande avbrott: kind (production/transmission), unit, zone, from_area, to_area, fuel, nuclear, unavailable_mw, installed_mw, from, to, reason, messages (antal meddelanden bakom posten), local (false = kärnkraft i angränsande elområde). |
reservoir | objekt | null | Vattenmagasin: ts, stored_mwh, fill_ratio (0–1), week_anomaly (mot normalt), change_4w. |
features | objekt | Snitt kommande 48 h: wind_index_local, wind_index_north, wind_index_south (1,00 = normal vind), temp_anomaly_<zon>_c, temp_local_c, solar_index_daytime (0–1), spread_proxy_se4_se2 (prognosens pris i SE4 minus SE2). |
zones.json — alla elområden
Samma huvudfält som ovan (generated_at, run_id,
unit, resolution, degraded,
default_model, fx) och zones[] med
zone, name, lat, lon,
eic (ENTSO-E:s områdeskod), current och relativa sökvägar
forecast_url, history_url, accuracy_url.
current är timmen då filen skrevs: ts, eur_mwh,
ore_kwh, source. Den blir inaktuell mellan körningarna, så en
klient som vill ha timmen just nu bör läsa den ur series.
zones/{SE1..SE4}/history.json — vad vi sa då
days (30), model_id (modellen historiken gäller),
lead_times_h ([24, 48, … 168]),
default_lead_time_h och points[] med ts,
actual och forecast — ett objekt från ledtid till p50,
t.ex. {"24": 5.8, "48": 12.1}: prognosen som gavs ungefär så många timmar
före leveranstimmen (±12 h). En ledtid saknas om ingen prognos gavs just då.
accuracy.json och zones/{…}/accuracy.json — träffsäkerhet
window_days är ett tak; scored_from och scored_to
är de leveranstimmar som faktiskt mätts. buckets är avstånden
(0-24h … 144-168h), reference_model referensen.
Måtten ligger i zones.<zon>.<modell>.<avstånd>
(per zon-fil: metrics.<modell>.<avstånd>) och
overall för hela landet:
| Fält | Innehåll |
|---|---|
n | Antal jämförda timmar. |
mae | Medelfel, EUR/MWh: snittet av avståndet mellan prognos och utfall. |
rmse | Medelfel som straffar stora missar hårdare. |
bias | Snittfel med tecken: positivt = för högt. |
coverage80 | Andel utfall inom p10–p90 (0–1), bör ligga nära 0,8. |
mape | Medelfel i procent av priset, bara timmar över 5 EUR/MWh. Kan vara null. |
p50_corr | Korrelation mellan prognos och utfall. Kan vara null. |
enough_data | false om n är under min_samples; visa då inte talen. |
skill_vs_naive | Andel mindre fel än referensen på samma timmar: 0,054 = 5,4 % mindre fel. Negativt = sämre. |
table är samma MAE kort: table.<ALL|zon>.<avstånd>.<modell>,
null där underlaget är för litet.
longterm.json — långtidsprognosen
default_model, reference_model, models[] och
zones.<zon> med recent_30d (snitt senaste 30 dagarna),
year_mean, drivers_sv[] och months[], en post per
kalendermånad:
| Fält | Innehåll |
|---|---|
month, label, horizon | 2026-11, november 2026, antal månader fram (1–3). |
models.<id>.p50 | Prognos för månadens snittpris. Standardmodellen har även p10/p90. |
models.lt_market | Terminsmarknaden: p50, tenor (month/quarter/year), delivery, contracts, system, epad, trade_date. Saknas om inget kontrakt täcker månaden. |
last_year | Samma månad förra året, faktiskt snittpris. |
nuclear_out_mw | Planerat kärnkraftsbortfall i snitt under månaden. |
outlook | Säsongsprognos för vädret: temp_anomaly_c, precip_anomaly_mm. Kan vara null. |
backtest.zones.<zon|ALL>.<modell>.<horisont> har
mae, bias och n; accuracy räknar
skarpa prognoser när månaderna är slut.
status.json och models.json
status.json: ok, generated_at,
run_id, degraded, demo, fx,
next_expected_update_utc (UTC) och sources — en post per
datakälla med ok, källspecifika räknare (rows,
messages …) och error när något gick fel.
models.json: default_model och models[] med
id, name_sv, description_sv,
quantiles, derived, is_default,
is_reference.
Räcker det för en Home Assistant-integration?
Ja. Allt en integration behöver finns i zones/<zon>/forecast.json:
aktuellt pris och prognos i series, vilken modell som gäller i
default_model och växelkursen i fx. Hämta var trettionde minut,
välj timmen ur series med din egen klocka, räkna öre med
fx.rate, hantera actual: null och visa en varning när
degraded är sant. Exemplen nedan gör detta med Home Assistants inbyggda
rest- och template-integrationer, utan egen kod.
Exempel
curl -s https://hakannormark.github.io/power-price-oracle/api/v1/zones/SE3/forecast.json | jq '.series[0]'
{
"ts": "2026-09-04T00:00:00+02:00",
"actual": 54.1,
"source": "official",
"models": {
"seasonal_naive": { "p10": 40.0, "p50": 52.0, "p90": 80.0 },
"shrunk_scaled": { "p10": 17.3, "p50": 55.0, "p90": 142.1 },
"market_scaled": { "p10": 21.6, "p50": 59.3, "p90": 146.4 }
}
}
Förkortat: models har en post per modell i
models.json. Bandet är brett och asymmetriskt med avsikt — se
Metod.
# Aktuellt pris i alla fyra elområden, i öre/kWh
curl -s https://hakannormark.github.io/power-price-oracle/api/v1/zones.json \
| jq -r '.zones[] | "\(.zone) \(.current.ore_kwh) öre/kWh (\(.current.source))"'
Home Assistant
Hämta hela serien som attribut på en sensor:
rest:
- resource: https://hakannormark.github.io/power-price-oracle/api/v1/zones/SE3/forecast.json
scan_interval: 1800
sensor:
- name: Spotprognos SE3
unique_id: spotprognos_se3
value_template: "{{ value_json.generated_at }}"
device_class: timestamp
json_attributes:
- series
- drivers
- default_model
- fx
- unit
Och plocka ut den aktuella timmens pris ur serien med en template-sensor.
Den tar det officiella priset när auktionen är klar och faller annars tillbaka
på standardmodellens p50. Omräkningen till öre använder växelkursen i
fx:
template:
- sensor:
- name: Spotpris SE3 nu
unique_id: spotpris_se3_nu
unit_of_measurement: "öre/kWh"
state_class: measurement
state: >
{% set hour = now().replace(minute=0, second=0, microsecond=0).isoformat() %}
{% set series = state_attr('sensor.spotprognos_se3', 'series') or [] %}
{% set model = state_attr('sensor.spotprognos_se3', 'default_model') %}
{% set fx = state_attr('sensor.spotprognos_se3', 'fx') %}
{% set rate = fx.rate if fx else none %}
{% set match = series | selectattr('ts', 'match', hour[:13]) | list | first %}
{% if match is none or rate is none %}
unknown
{% elif match.actual is not none %}
{{ (match.actual * rate / 10) | round(1) }}
{% else %}
{{ (match.models[model].p50 * rate / 10) | round(1) }}
{% endif %}
attributes:
källa: >
{% set hour = now().replace(minute=0, second=0, microsecond=0).isoformat() %}
{% set series = state_attr('sensor.spotprognos_se3', 'series') or [] %}
{% set match = series | selectattr('ts', 'match', hour[:13]) | list | first %}
{{ match.source if match is not none else 'unknown' }}
Byt SE3 mot ditt elområde. Vill du styra automationer på
prognosen: läs source först. Är den official är priset
satt av börsen, är den forecast är det vår gissning och bör hanteras
med bandet p10–p90 i åtanke.