Dokumentace API

spotovky.cz poskytuje spotové ceny elektřiny z OTE-CR přes REST API a MQTT broker. Vše je zdarma, bez registrace, bez API klíče.

Rychlý start

Nejrychlejší způsob jak získat data — stačí jedna HTTP žádost nebo MQTT subscribe.

REST API

cURL
curl https://api.spotovky.cz/v2/prices/today

MQTT

mosquitto_sub
mosquitto_sub -h spotovky.cz -t "v2/current/json"

Díky retain zprávám na MQTT dostanete data okamžitě po přihlášení — nemusíte čekat na další aktualizaci.

Verze 2 je doporučená pro nové projekty: přesné časy i v den změny času, záporné ceny i v binárním formátu, cena v EUR/MWh a odpověď 404 místo náhradních dat. Verze 1 zůstává beze změny pro stávající zařízení.

REST API

Base URL: https://api.spotovky.cz. Odpovědi jsou ve formátu JSON (Content-Type: application/json), CORS je povolen pro všechny domény.

Endpointy

GET /v2/prices/today

Ceny pro dnešní den (středoevropský čas, SEČ/SELČ).

GET /v2/prices/tomorrow

Ceny pro zítřek. Dostupné obvykle od 13:05, jakmile OTE-CR zveřejní výsledky aukce; po 14:30 se ještě upřesní kurz ČNB. Do té doby HTTP 404.

GET /v2/prices/yesterday

Ceny pro včerejšek.

GET /v2/prices/{datum}

Ceny pro konkrétní den ve formátu YYYY-MM-DD, např. /v2/prices/2026-03-13.

• Data pro den nejsou → HTTP 404 (nikdy se nevrací jiný den)
• Neplatné datum (např. 2026-02-31) → HTTP 400

GET /v2/stats?month={YYYY-MM}

Souhrn dnů měsíce: průměr, minimum, maximum a počet záporných intervalů. Viz Souhrn měsíce.

GET /v2/tariffs?date={datum}&distributor={id}&rate={sazba}

Ceny distribuce a regulované složky podle cenového rozhodnutí ERÚ platné pro daný den (bez DPH). Všechny parametry jsou nepovinné; bez date platí dnešek. Viz Ceny distribuce.

GET /v2/health

Stav služby a čerstvost dat. HTTP 200, když je vše v pořádku, jinak HTTP 503. Viz Stav služby.

Historická data jsou dostupná od spuštění služby (březen 2026). Minulé dny se cachují 1 hodinu, dnešek a zítřek 5 minut, odpověď 404 jen 1 minutu.

Formát odpovědi (JSON v2)

Stejný formát mají i MQTT zprávy v2/today/json a v2/tomorrow/json.

Příklad odpovědi — /v2/prices/2026-10-25 (den změny času)
{
  "version": 2,
  "date": "2026-10-25",          // den dodávky (Europe/Prague)
  "resolution": "PT15M",         // 15minutové intervaly
  "complete": true,              // den má všechny intervaly (92 / 96 / 100)
  "negative": false,             // den obsahuje zápornou cenu
  "rate": 24.335,                // kurz EUR/CZK (ČNB, jak ho používá OTE)
  "slots": [
    { "start": "2026-10-25T02:00:00+02:00", "czk": 4.07, "eur": 167.25, "rank": 12 },
    // … letní čas končí ve 3:00, hodina 02:00–02:59 se opakuje v zimním čase:
    { "start": "2026-10-25T02:00:00+01:00", "czk": 3.95, "eur": 162.32, "rank": 9 }
    // … celkem 92, 96 nebo 100 intervalů
  ]
}
PoleTypPopis
versionintegerVerze formátu (2)
datestringDen dodávky YYYY-MM-DD (časové pásmo Europe/Prague)
resolutionstringDélka intervalu podle ISO 8601 (PT15M)
completebooleanDen obsahuje všechny intervaly
negativebooleanDen obsahuje aspoň jednu zápornou cenu
ratenumberKurz EUR/CZK, kterým OTE přepočítává ceny daného dne
slots[].startstringZačátek intervalu jako ISO 8601 s posunem (+01:00 zimní / +02:00 letní čas) — jednoznačný i v den změny času
slots[].czknumberSpotová cena v Kč/kWh bez DPH, 2 desetinná místa. Může být záporná.
slots[].eurnumberSpotová cena v EUR/MWh, jak ji zveřejňuje OTE
slots[].rankintegerPořadí intervalu v rámci dne (1 = nejlevnější)

Záporné ceny — při přebytku solární nebo větrné energie mohou být spotové ceny záporné. Pole czk a eur mohou obsahovat zápornou hodnotu, počítejte s tím ve svém kódu.

HTTP status kódy

KódPopis
200 OKÚspěch. Odpověď obsahuje data.
400 Bad RequestNeplatné datum v URL. Odpověď: {"error": "Invalid date ..."}
404 Not FoundPro daný den nejsou data (např. zítřek před zveřejněním). Odpověď: {"error": "No data for 2026-10-26"}

Souhrn měsíce

Pro každý den měsíce, který má data, vrací průměr, minimum a maximum v Kč/kWh (zaokrouhleno na 2 desetinná místa), čas začátku nejlevnějšího a nejdražšího intervalu, počet intervalů se zápornou cenou a průměr v EUR/MWh. Dny bez dat v odpovědi nejsou. Stejná data ukazuje kalendář cen.

Příklad odpovědi — /v2/stats?month=2026-10 (zkráceno)
{
  "version": 2,
  "month": "2026-10",
  "days": [
    {
      "date": "2026-10-09",
      "periods": 96,
      "complete": true,
      "avg": 2.38,
      "min": -0.11,
      "max": 5.04,
      "min_start": "2026-10-09T23:45:00+02:00",
      "max_start": "2026-10-09T10:00:00+02:00",
      "negative_periods": 1,
      "avg_eur": 97.87
    },
    …
  ]
}

Neplatný měsíc → HTTP 400, měsíc bez dat → HTTP 404. Minulé měsíce se cachují 1 hodinu, aktuální 5 minut.

Ceny distribuce (ERÚ)

Pevné ceny distribuce elektřiny pro hladinu nízkého napětí (sazby C i D) a regulované složky ceny. Zdrojem je tabulka z cenového rozhodnutí Energetického regulačního úřadu; server ji kontroluje každý týden a nový ceník převezme, jakmile ERÚ zveřejní nový soubor. Ceny jsou bez DPH a bez daně z elektřiny.

ParametrPopis
dateDen ve formátu YYYY-MM-DD; výchozí je dnešek. Vrátí ceník platný v ten den.
distributorcez (ČEZ Distribuce), egd (EG.D), pre (PREdistribuce), uced, sv
rateDistribuční sazba, např. D02d, D57d, C25d
Příklad odpovědi — /v2/tariffs?distributor=cez&rate=D57d (zkráceno)
{
  "version": 2,
  "date": "2026-10-23",
  "valid_from": "2026-01-01",
  "valid_to": null,
  "vat_included": false,
  "source": { "name": "ERÚ, cenové rozhodnutí — ceny distribuce NN", "url": "https://eru.gov.cz/…/ceny-nn260.xlsx", … },
  "regulated": {
    "system_services": { "value": 164.24, "unit": "Kč/MWh" },
    "poze":            { "value": 8.32,   "unit": "Kč/A/měsíc" },
    "poze_cap":        { "value": 495,    "unit": "Kč/MWh" },
    "non_grid":        { "value": 12.87,  "unit": "Kč/měsíc" }
  },
  "distributors": {
    "cez": {
      "name": "ČEZ Distribuce",
      "rates": {
        "D57d": {
          "category": "D",
          "title": "Dvoutarifová sazba pro vytápění topným elektrickým spotřebičem …",
          "two_tariff": true,
          "energy_czk_mwh": { "VT": 754.77, "NT": 116.5 },
          "fixed": [
            { "kind": "band", "phases": 3, "from_a": 0, "to_a": 10, "czk_month": 222, "label": "jistič do 3x10 A a do 1x25 A včetně" },
            …
            { "kind": "per_amp", "phases": 3, "from_a": 160, "to_a": null, "czk_month": 122.49, "label": "jistič nad 3x160 A za každou 1A" }
          ]
        }
      }
    }
  }
}

energy_czk_mwh obsahuje VT a NT u dvoutarifových sazeb, jinak SINGLE. Položky fixed jsou měsíční platy: band za jistič v daném pásmu, per_amp příplatek za každý ampér nad hranicí, per_10w a flat u neměřených odběrů. Pro den bez platného ceníku vrací HTTP 404.

Stav služby

/v2/health se hodí pro monitoring (např. Uptime Kuma). Kontroluje, že jsou v databázi všechny intervaly dneška, od 14:00 i zítřka, že poslední úspěšné stažení z OTE-CR není starší než 26 hodin a že se aktuální interval publikoval na MQTT v posledních 20 minutách. Odpověď se necachuje.

Příklad odpovědi
{
  "status": "ok",
  "time": "2026-10-23T14:20:00+02:00",
  "checks": {
    "today":    { "ok": true, "periods": 96, "expected": 96 },
    "tomorrow": { "ok": true, "periods": 100, "due": true },
    "fetch":    { "ok": true, "last_success_min_ago": 5, "last_error": null },
    "publish":  { "ok": true, "last_min_ago": 5, "last_error": null }
  }
}

MQTT

Broker je dostupný na adrese spotovky.cz, port 1883. Připojení nevyžaduje uživatelské jméno ani heslo.

Připojení
# mosquitto_sub
mosquitto_sub -h spotovky.cz -p 1883 -t "v2/today/json"

# Python (paho-mqtt)
import paho.mqtt.client as mqtt
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.connect("spotovky.cz", 1883)
client.subscribe("v2/current/json")

Topicy

TopicFormátPopis
v2/today/jsonJSON v2Dnešní ceny — všechny intervaly (stejné jako REST /v2/prices/today). Retain, QoS 1.
v2/today/binBinární v2Dnešní ceny — kompaktní binární formát včetně záporných cen. Retain, QoS 1.
v2/tomorrow/jsonJSON v2Zítřejší ceny — obvykle od ~13:05. Retain, QoS 1. Po půlnoci smazáno.
v2/tomorrow/binBinární v2Zítřejší ceny — binární formát v2. Retain, QoS 1. Po půlnoci smazáno.
v2/current/jsonJSON v2Aktuální 15minutový interval. Retain, QoS 1. Aktualizuje se každých 15 minut.
v2/current/binBinární v2Aktuální interval — binární formát v2 (16 bytů). Retain, QoS 1.

Zpráva v2/current/json obsahuje jen aktuální interval:

v2/current/json
{
  "version": 2,
  "start": "2026-10-23T14:15:00+02:00",   // začátek intervalu
  "end": "2026-10-23T14:30:00+02:00",     // konec intervalu
  "czk": 1.82,                            // Kč/kWh bez DPH
  "eur": 74.79,                           // EUR/MWh
  "rank": 12,                             // pořadí v rámci dne
  "periods": 96                           // počet intervalů dne
}

Topicy verze 1 (v1/…) jsou popsané v sekci Verze 1.

Pravidla přístupu

Publish je zakázán. Broker přijímá pouze subscribe. Pokus o publish na jakýkoliv topic bude tiše odmítnut — zpráva nebude doručena a připojení zůstane aktivní.

Povolené topicy. Subscribe je možný pouze na topicy uvedené v dokumentaci (v2 i v1). Subscribe na jiný topic bude odmítnut.

Wildcard subscribe (#, +) není povolen. Vždy se přihlašujte na konkrétní topic.

Opakované pokusy o zakázané operace mohou vést k dočasnému zablokování IP adresy.

Harmonogram aktualizací

Čas (SEČ/SELČ)AkceOvlivněné topicy (v2 i v1)
00:00Publikace dnešních cen, smazání zítřejších retain zprávtoday/json, today/bin, tomorrow/json*, tomorrow/bin*
každých 15 minAktualizace aktuálního intervalucurrent/json, current/bin
13:05–14:55Stažení dat z OTE-CR každých 15 minut; jakmile jsou zítřejší ceny kompletní, ihned se publikujítoday/*, tomorrow/*
15:00Publikace dnešních a zítřejších cen s finálním kurzem ČNBtoday/*, tomorrow/*

* Smazání retain zprávy = publish prázdné zprávy na daný topic s retain=true, standardní MQTT mechanismus.

Datové formáty

JSON formát

Popis JSON v2 viz Formát odpovědi — REST /v2/prices/… a MQTT v2/today/json, v2/tomorrow/json mají identickou strukturu.

Binární formát v2 — včetně záporných cen

Formát v2 nese ceny jako int16 se znaménkem, takže zachová i záporné ceny. Navíc má bajt verze, příznaky a přesnější kurz. Topicy: v2/today/bin, v2/tomorrow/bin, v2/current/bin. Formát v1 zůstává beze změny pro stávající zařízení.

Pro nové projekty doporučujeme v2. Vždy nejdřív zkontrolujte bajt verze na offsetu 0 (hodnota 2) a délku zprávy.

v2/today/bin a v2/tomorrow/bin

OffsetVelikostTypObsah
01 Buint8Verze formátu = 2
11 Buint8Příznaky: bit 0 = den je kompletní, bit 1 = den obsahuje zápornou cenu
28 Buint64Unix timestamp půlnoci daného dne v časovém pásmu Europe/Prague
102 Buint16Kurz EUR/CZK × 1000 (např. 24335 = 24,335 Kč/EUR)
121 Buint8Počet period N (92, 96 nebo 100)
13N × 2 BN × int16Ceny v haléřích se znaménkem (czk/kWh × 100, rozsah ±327,67 Kč/kWh); hodnota −32768 je vyhrazena pro chybějící cenu
13 + N×2N × 1 BN × uint8Pořadí intervalu (1 = nejlevnější, N = nejdražší)

Celková délka: 13 + N×3 bytů (289 B pro 92 period / 301 B pro 96 / 313 B pro 100). Čas slotu: midnight_unix + index × 900.

v2/current/bin

OffsetVelikostTypObsah
01 Buint8Verze formátu = 2
11 Buint8Příznaky dne (stejné jako u přehledu dne)
28 Buint64Unix timestamp začátku aktuálního intervalu (UTC)
102 Bint16Cena v haléřích se znaménkem
121 Buint8Pořadí intervalu v rámci dne (1 = nejlevnější)
132 Buint16Kurz EUR/CZK × 1000
151 Buint8Počet period dne N

Celková délka: vždy 16 bytů.

Dekódování v2/current/bin v C (ESP32 / Arduino)
// v2/current/bin — 16 bytů, little-endian
if (len < 16 || data[0] != 2) return;   // verze formátu 2

uint64_t start;
int16_t  halere;            // se znaménkem — záporné ceny
uint16_t rate_x1000;

memcpy(&start,      data + 2,  8);
memcpy(&halere,     data + 10, 2);
uint8_t rank    = data[12];
memcpy(&rate_x1000, data + 13, 2);
uint8_t periods = data[15];

bool  negative_day = data[1] & 0x02;
float czk     = halere     / 100.0f;
float eur_czk = rate_x1000 / 1000.0f;

Serial.printf("%.2f Kč/kWh, pořadí %d/%d, kurz %.3f\n", czk, rank, periods, eur_czk);
Dekódování v2/today/bin v Pythonu
import struct, datetime, zoneinfo

def decode_day_v2(data: bytes):
    version, flags, midnight, rate_x1000, n = struct.unpack_from("<BBQHB", data, 0)
    assert version == 2
    prices = struct.unpack_from(f"<{n}h", data, 13)          # h = int16 se znaménkem
    ranks  = struct.unpack_from(f"<{n}B", data, 13 + n * 2)

    tz = zoneinfo.ZoneInfo("Europe/Prague")
    for i, (halere, rank) in enumerate(zip(prices, ranks)):
        if halere == -32768:
            continue                                   # cena chybí
        dt = datetime.datetime.fromtimestamp(midnight + i * 900, tz)
        print(f"{dt:%H:%M}  {halere / 100:6.2f} Kč/kWh  rank {rank}/{n}")

Letní a zimní čas (DST)

Počet slotů za den není vždy 96. OTE-CR publikuje skutečný počet 15minutových intervalů:

SituacePočet slotůVelikost today/bin (v2 / v1)
Přechod na letní čas (poslední neděle v březnu)92289 B / 287 B
Běžný den96301 B / 299 B
Přechod na zimní čas (poslední neděle v říjnu)100313 B / 311 B

Pole slots v JSON vždy obsahuje správný počet záznamů. V binárním formátu je počet period uložen v bytu na offsetu 12 (v2), resp. 10 (v1) — vždy ho čtěte před parsováním dat. V JSON v2 má každý interval přesný čas start s posunem, takže opakovaná hodina 02:00 v říjnu je jednoznačná.

Nepředpokládejte pevný počet 96 slotů. Vždy použijte hodnotu N z binárního payloadu nebo délku pole slots z JSON.

Verze 1 (starší)

Verze 1 zůstává v provozu beze změny kvůli stávajícím zařízením. Pro nové projekty použijte verzi 2. Rozdíly: v1 má čas intervalu jen jako „HH:MM“ (v den změny času se 02:00 opakuje), binární v1 ořezává záporné ceny na 0 a REST v1 při chybějících datech vrací data jiného dne.

REST v1 — endpointy

GET /v1/today

Vrátí data pro dnešní datum ve středoevropském čase (SEČ/SELČ).

GET /v1/tomorrow

Vrátí data pro zítřejší datum. Dostupné obvykle od 13:05, jakmile OTE-CR zveřejní výsledky aukce; po 14:30 se ještě upřesní kurz ČNB.

Pozor: pokud data pro zítřek nejsou k dispozici, vrátí dnešní data.

GET /v1/yesterday

Vrátí data pro včerejší datum.

GET /v1/{datum}

Vrátí data pro konkrétní datum ve formátu YYYY-MM-DD, např. /v1/2026-03-13.

• Datum v budoucnu (dále než zítřek) → vrátí zítřejší data
• Neplatný formát data → HTTP 400
• Data pro datum nejsou dostupná → vrátí dnešní data

Historická data jsou dostupná od spuštění služby. Starší záznamy mohou chybět.

REST v1 — formát odpovědi

Každá odpověď obsahuje datum, kurz EUR/CZK a pole slots s 15minutovými intervaly.

Příklad odpovědi — /v1/today
{
  "date": "2026-03-13",       // datum (Europe/Prague)
  "rate": 24.43,              // kurz EUR/CZK z ČNB
  "slots": [
    {
      "t":    "00:00",           // začátek intervalu (SEČ/SELČ)
      "czk":  2.60,             // cena v Kč/kWh (zaokrouhleno na 2 des. místa)
      "rank": 55               // pořadí od nejlevnější (1 = nejlevnější)
    },
    {
      "t":    "00:15",
      "czk":  2.31,
      "rank": 52
    },
    // ... celkem 92, 96 nebo 100 slotů (viz sekce Letní/zimní čas)
  ]
}
Pole Typ Popis
datestringDatum ve formátu YYYY-MM-DD (časové pásmo Europe/Prague)
ratenumberKurz EUR/CZK z ČNB platný pro daný den
slotsarrayPole 15minutových intervalů, seřazené chronologicky
slots[].tstringZačátek intervalu ve formátu HH:MM, místní čas Europe/Prague (SEČ/SELČ)
slots[].czknumberSpotová cena v Kč/kWh, zaokrouhlena na 2 desetinná místa. Může být záporná.
slots[].rankintegerPořadí intervalu v rámci dne od nejlevnějšího (1 = nejlevnější)

Záporné ceny — při přebytku solární nebo větrné energie mohou být spotové ceny záporné. Pole czk může obsahovat zápornou hodnotu, počítejte s tím ve svém kódu.

REST v1 — HTTP status kódy

KódPopis
200 OKÚspěch. Odpověď obsahuje data.
400 Bad RequestNeplatný formát data v URL (např. /v1/abc). Odpověď: {"error": "Invalid date format..."}
503 Service UnavailableData nejsou dostupná ani pro dnešek (výpadek fetcheru). Odpověď: {"error": "No data available"}

MQTT v1 — topicy

TopicFormátPopis
v1/today/jsonJSON v1Dnešní ceny (stejné jako REST /v1/today). Retain, QoS 1.
v1/today/binBinární v1Dnešní ceny — binární formát v1 (záporné ceny oříznuté na 0). Retain, QoS 1.
v1/tomorrow/jsonJSON v1Zítřejší ceny — obvykle od ~13:05. Retain, QoS 1. Po půlnoci smazáno.
v1/tomorrow/binBinární v1Zítřejší ceny — binární formát v1. Retain, QoS 1. Po půlnoci smazáno.
v1/current/jsonJSON v1Aktuální interval: {"t": "14:15", "czk": 1.82, "rank": 12}. Retain, QoS 1.
v1/current/binBinární v1Aktuální interval — 13 bytů. Retain, QoS 1.

Binární formát v1

Kompaktní formát pro embedded zařízení. Všechny hodnoty jsou little-endian.

today/bin a tomorrow/bin

OffsetVelikostTypObsah
08 Buint64Unix timestamp půlnoci daného dne v časovém pásmu Europe/Prague
82 Buint16Kurz EUR/CZK × 100 (např. 2443 = 24.43 Kč/EUR)
101 Buint8Počet period N (92, 96 nebo 100 — viz letní/zimní čas)
11N × 2 BN × uint16Ceny v haléřích (czk/kWh × 100), záporné ceny jsou oříznuty na 0 — pro záporné ceny použijte formát v2
11 + N×2N × 1 BN × uint8Pořadí intervalu (1 = nejlevnější, N = nejdražší)

Celková délka: 11 + N×3 bytů (287 B pro 92 period / 299 B pro 96 / 311 B pro 100).

Index slotu odpovídá pořadí od půlnoci: slot 0 = 00:00, slot 1 = 00:15, atd. Čas slotu: midnight_unix + index × 900.

current/bin

OffsetVelikostTypObsah
08 Buint64Unix timestamp začátku aktuálního intervalu (UTC)
82 Buint16Cena v haléřích (czk/kWh × 100), min. 0 — záporné ceny viz formát v2
101 Buint8Pořadí intervalu v rámci dne (1 = nejlevnější)
112 Buint16Kurz EUR/CZK × 100

Celková délka: vždy 13 bytů.

Dekódování v C (ESP32 / Arduino)
// current/bin — 13 bytů
uint64_t timestamp;
uint16_t halere, rate_x100;
uint8_t  rank;

memcpy(&timestamp,  data + 0,  8);
memcpy(&halere,     data + 8,  2);
rank = data[10];
memcpy(&rate_x100,  data + 11, 2);

float czk     = halere    / 100.0f;
float eur_czk = rate_x100 / 100.0f;

Serial.printf("%.2f Kč/kWh, rank %d, kurz %.2f\n", czk, rank, eur_czk);
Dekódování today/bin v Pythonu
import struct, datetime, zoneinfo

def decode_day_bin(data: bytes):
    midnight, rate_x100, n = struct.unpack_from("<QHB", data, 0)
    prices = struct.unpack_from(f"<{n}H", data, 11)
    ranks  = struct.unpack_from(f"<{n}B", data, 11 + n * 2)

    eur_czk = rate_x100 / 100
    tz = zoneinfo.ZoneInfo("Europe/Prague")

    for i, (halere, rank) in enumerate(zip(prices, ranks)):
        slot_ts = midnight + i * 900
        dt = datetime.datetime.fromtimestamp(slot_ts, tz)
        czk = halere / 100
        print(f"{dt:%H:%M}  {czk:.2f} Kč/kWh  rank {rank}/{n}")

Limity a podmínky

Dostupnost

Služba je provozována jako hobby projekt bez záruky dostupnosti. Nejsou poskytovány žádné SLA. Data mohou chybět v případě nedostupnosti OTE-CR API nebo výpadku serveru.

Zdroje dat

Spotové ceny pocházejí z OTE-CR (Operátor trhu s elektřinou). Ceny distribuce a regulované složky pocházejí z cenového rozhodnutí ERÚ (Energetický regulační úřad). Kurz EUR/CZK pochází z ČNB (Česká národní banka) a je aktualizován jednou denně po 14:30. Pro víkendy a svátky se používá poslední dostupný kurz.

Ceny v /v2/prices jsou pouze spotové — neobsahují distribuci, daně ani poplatky. Ceny distribuce a regulované složky jsou zvlášť v /v2/tariffs. Nejsou vhodné jako podklad pro fakturaci bez dalšího zpracování.

Použití

Data jsou veřejná a jejich použití není omezeno. Pokud službu využíváte ve svém projektu, oceníme zmínku o zdrojích dat (OTE-CR, spotovky.cz).

Kontakt

Dotazy, hlášení chyb nebo návrhy pište na martin@lysek.cz.