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 https://api.spotovky.cz/v2/prices/today
MQTT
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
Ceny pro dnešní den (středoevropský čas, SEČ/SELČ).
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.
Ceny pro včerejšek.
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
Souhrn dnů měsíce: průměr, minimum, maximum a počet záporných intervalů. Viz Souhrn měsíce.
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.
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.
{
"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ů
]
}
| Pole | Typ | Popis |
|---|---|---|
| version | integer | Verze formátu (2) |
| date | string | Den dodávky YYYY-MM-DD (časové pásmo Europe/Prague) |
| resolution | string | Délka intervalu podle ISO 8601 (PT15M) |
| complete | boolean | Den obsahuje všechny intervaly |
| negative | boolean | Den obsahuje aspoň jednu zápornou cenu |
| rate | number | Kurz EUR/CZK, kterým OTE přepočítává ceny daného dne |
| slots[].start | string | Začátek intervalu jako ISO 8601 s posunem (+01:00 zimní / +02:00 letní čas) — jednoznačný i v den změny času |
| slots[].czk | number | Spotová cena v Kč/kWh bez DPH, 2 desetinná místa. Může být záporná. |
| slots[].eur | number | Spotová cena v EUR/MWh, jak ji zveřejňuje OTE |
| slots[].rank | integer | Poř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ód | Popis |
|---|---|
| 200 OK | Úspěch. Odpověď obsahuje data. |
| 400 Bad Request | Neplatné datum v URL. Odpověď: {"error": "Invalid date ..."} |
| 404 Not Found | Pro 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.
{
"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.
| Parametr | Popis |
|---|---|
| date | Den ve formátu YYYY-MM-DD; výchozí je dnešek. Vrátí ceník platný v ten den. |
| distributor | cez (ČEZ Distribuce), egd (EG.D), pre (PREdistribuce), uced, sv |
| rate | Distribuční sazba, např. D02d, D57d, C25d |
{
"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.
{
"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.
# 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
| Topic | Formát | Popis |
|---|---|---|
| v2/today/json | JSON v2 | Dnešní ceny — všechny intervaly (stejné jako REST /v2/prices/today). Retain, QoS 1. |
| v2/today/bin | Binární v2 | Dnešní ceny — kompaktní binární formát včetně záporných cen. Retain, QoS 1. |
| v2/tomorrow/json | JSON v2 | Zítřejší ceny — obvykle od ~13:05. Retain, QoS 1. Po půlnoci smazáno. |
| v2/tomorrow/bin | Binární v2 | Zítřejší ceny — binární formát v2. Retain, QoS 1. Po půlnoci smazáno. |
| v2/current/json | JSON v2 | Aktuální 15minutový interval. Retain, QoS 1. Aktualizuje se každých 15 minut. |
| v2/current/bin | Binární v2 | Aktuální interval — binární formát v2 (16 bytů). Retain, QoS 1. |
Zpráva v2/current/json obsahuje jen aktuální interval:
{
"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Č) | Akce | Ovlivněné topicy (v2 i v1) |
|---|---|---|
| 00:00 | Publikace dnešních cen, smazání zítřejších retain zpráv | today/json, today/bin, tomorrow/json*, tomorrow/bin* |
| každých 15 min | Aktualizace aktuálního intervalu | current/json, current/bin |
| 13:05–14:55 | Stažení dat z OTE-CR každých 15 minut; jakmile jsou zítřejší ceny kompletní, ihned se publikují | today/*, tomorrow/* |
| 15:00 | Publikace dnešních a zítřejších cen s finálním kurzem ČNB | today/*, 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
| Offset | Velikost | Typ | Obsah |
|---|---|---|---|
| 0 | 1 B | uint8 | Verze formátu = 2 |
| 1 | 1 B | uint8 | Příznaky: bit 0 = den je kompletní, bit 1 = den obsahuje zápornou cenu |
| 2 | 8 B | uint64 | Unix timestamp půlnoci daného dne v časovém pásmu Europe/Prague |
| 10 | 2 B | uint16 | Kurz EUR/CZK × 1000 (např. 24335 = 24,335 Kč/EUR) |
| 12 | 1 B | uint8 | Počet period N (92, 96 nebo 100) |
| 13 | N × 2 B | N × int16 | Ceny 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×2 | N × 1 B | N × uint8 | Poř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
| Offset | Velikost | Typ | Obsah |
|---|---|---|---|
| 0 | 1 B | uint8 | Verze formátu = 2 |
| 1 | 1 B | uint8 | Příznaky dne (stejné jako u přehledu dne) |
| 2 | 8 B | uint64 | Unix timestamp začátku aktuálního intervalu (UTC) |
| 10 | 2 B | int16 | Cena v haléřích se znaménkem |
| 12 | 1 B | uint8 | Pořadí intervalu v rámci dne (1 = nejlevnější) |
| 13 | 2 B | uint16 | Kurz EUR/CZK × 1000 |
| 15 | 1 B | uint8 | Počet period dne N |
Celková délka: vždy 16 bytů.
// 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);
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ů:
| Situace | Počet slotů | Velikost today/bin (v2 / v1) |
|---|---|---|
| Přechod na letní čas (poslední neděle v březnu) | 92 | 289 B / 287 B |
| Běžný den | 96 | 301 B / 299 B |
| Přechod na zimní čas (poslední neděle v říjnu) | 100 | 313 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
Vrátí data pro dnešní datum ve středoevropském čase (SEČ/SELČ).
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.
Vrátí data pro včerejší 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.
{
"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 |
|---|---|---|
| date | string | Datum ve formátu YYYY-MM-DD (časové pásmo Europe/Prague) |
| rate | number | Kurz EUR/CZK z ČNB platný pro daný den |
| slots | array | Pole 15minutových intervalů, seřazené chronologicky |
| slots[].t | string | Začátek intervalu ve formátu HH:MM, místní čas Europe/Prague (SEČ/SELČ) |
| slots[].czk | number | Spotová cena v Kč/kWh, zaokrouhlena na 2 desetinná místa. Může být záporná. |
| slots[].rank | integer | Poř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ód | Popis |
|---|---|
| 200 OK | Úspěch. Odpověď obsahuje data. |
| 400 Bad Request | Neplatný formát data v URL (např. /v1/abc). Odpověď: {"error": "Invalid date format..."} |
| 503 Service Unavailable | Data nejsou dostupná ani pro dnešek (výpadek fetcheru). Odpověď: {"error": "No data available"} |
MQTT v1 — topicy
| Topic | Formát | Popis |
|---|---|---|
| v1/today/json | JSON v1 | Dnešní ceny (stejné jako REST /v1/today). Retain, QoS 1. |
| v1/today/bin | Binární v1 | Dnešní ceny — binární formát v1 (záporné ceny oříznuté na 0). Retain, QoS 1. |
| v1/tomorrow/json | JSON v1 | Zítřejší ceny — obvykle od ~13:05. Retain, QoS 1. Po půlnoci smazáno. |
| v1/tomorrow/bin | Binární v1 | Zítřejší ceny — binární formát v1. Retain, QoS 1. Po půlnoci smazáno. |
| v1/current/json | JSON v1 | Aktuální interval: {"t": "14:15", "czk": 1.82, "rank": 12}. Retain, QoS 1. |
| v1/current/bin | Binární v1 | Aktuá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
| Offset | Velikost | Typ | Obsah |
|---|---|---|---|
| 0 | 8 B | uint64 | Unix timestamp půlnoci daného dne v časovém pásmu Europe/Prague |
| 8 | 2 B | uint16 | Kurz EUR/CZK × 100 (např. 2443 = 24.43 Kč/EUR) |
| 10 | 1 B | uint8 | Počet period N (92, 96 nebo 100 — viz letní/zimní čas) |
| 11 | N × 2 B | N × uint16 | Ceny 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×2 | N × 1 B | N × uint8 | Poř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
| Offset | Velikost | Typ | Obsah |
|---|---|---|---|
| 0 | 8 B | uint64 | Unix timestamp začátku aktuálního intervalu (UTC) |
| 8 | 2 B | uint16 | Cena v haléřích (czk/kWh × 100), min. 0 — záporné ceny viz formát v2 |
| 10 | 1 B | uint8 | Pořadí intervalu v rámci dne (1 = nejlevnější) |
| 11 | 2 B | uint16 | Kurz EUR/CZK × 100 |
Celková délka: vždy 13 bytů.
// current/bin — 13 bytů uint64_t timestamp; uint16_t halere, rate_x100; uint8_t rank; memcpy(×tamp, 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);
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.