Evidka API
Strojově čitelná specifikace: /openapi.json (OpenAPI 3.1) – vygenerujete z ní klienta pro PHP, C#, Javu či Python (openapi-generator).
Evidence tržeb EET 2.0 (zákon č. 180/2026 Sb.) jedním JSON požadavkem. Evidka sestaví datovou zprávu podle rozhraní v4, podepíše ji vaším pokladním certifikátem (WS-Security, RSA-SHA256, exc-c14n), odešle ji na společné technické zařízení finanční správy a vrátí potvrzovací kód (POK). Když finanční správa neodpoví včas, tržbu drží ve frontě a doposílá ji, dokud neuplyne zákonná lhůta 48 hodin.
- Všechny požadavky i odpovědi jsou JSON v UTF-8, klíče jsou česky (bez diakritiky).
- Částky posílejte jako řetězec
"1249.50"nebo číslo v Kč. Desetinná čárka ("1249,50") je také v pořádku. - Časy jsou ISO 8601. Tržby se do EET posílají v čase ČR (
+01:00/+02:00), převod řešíme my.
Rychlý start
- Získejte API klíč: na vyzkoušení tlačítkem „Vyzkoušet hned“ (zkušební účet s testovacím certifikátem finanční správy, nic dalšího nenahráváte). Pro ostrý provoz vám účet založíme a v dashboardu nahrajete pokladní certifikát (.p12) z DIS+.
- Pošlete tržbu:
curl -X POST https://evidka.cz/v1/trzby \
-H "Authorization: Bearer evk_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: objednavka-1001" \
-d '{ "idJednotky": 303, "idPokl": "VYDEJNA-1", "castka": "1249.00", "zpusobPlatby": "karta" }'
Odpověď 201:
{
"id": "trz_4sbW3uomXH3_",
"stav": "prijato",
"pok": "1b10b4dd-a403-4141-b71a-986a62bce660-ff",
"test": true,
"prostredi": "playground",
"eic": "CZ00000019",
"idJednotky": "303",
"idPokl": "VYDEJNA-1",
"poradCis": "2027/1",
"datTrzby": "2027-01-07T10:15:02+01:00",
"castka": "1249.00",
"zpusobPlatby": "karta",
"varovani": [],
"chyba": null,
"pokusy": 1,
"lhutaDoposlani": "2027-01-09T09:15:02.000Z",
"poLhute": false
}
POK, pořadové číslo a čas tržby si uložte k dokladu. Na playgroundu končí POK na -ff a není platný. Pole podpisFS říká, zda Evidka ověřila elektronický podpis potvrzení od finanční správy (certifikát GFŘ vydaný I.CA).
Node.js / TypeScript
const res = await fetch(`${EVIDKA_URL}/v1/trzby`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.EVIDKA_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': order.id,
},
body: JSON.stringify({ idJednotky: 303, idPokl: 'ESHOP', castka: order.total, zpusobPlatby: 'karta' }),
});
const trzba = await res.json();
if (trzba.stav === 'prijato') saveReceipt(order.id, trzba.pok, trzba.poradCis);
else if (trzba.stav === 'fronta') saveReceipt(order.id, null, trzba.poradCis); // POK dorazí webhookem
PHP
$ch = curl_init("$evidkaUrl/v1/trzby");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $key", 'Content-Type: application/json', "Idempotency-Key: $orderId"],
CURLOPT_POSTFIELDS => json_encode(['idJednotky' => 303, 'castka' => '1249.00', 'zpusobPlatby' => 'hotove']),
]);
$trzba = json_decode(curl_exec($ch), true);
Zkušební účet za vteřinu
curl -X POST https://evidka.cz/v1/sandbox
→ 201 { "klic": "evk_live_…", "platnostDni": 7, "limitTrzebZa24h": 200, "dashboard": "/app/#klic=…", "pokladna": "/pokladna/#klic=…" }
Zkušební účet má nahraný veřejný testovací certifikát finanční správy (fiktivní firma CZ00000019) a posílá na playground. Platí 7 dní, limit je 200 tržeb za 24 hodin. Produkční certifikát do něj nahrát nejde.
Ukázky kódu
Odeslání tržby s Idempotency-Key (číslo dokladu) a rozlišením přijato / ve frontě / chyba. API klíč dejte do proměnné prostředí EVIDKA_KEY; zkušební dostanete tlačítkem „Vyzkoušet hned“ nebo curl -X POST https://evidka.cz/v1/sandbox.
<?php
// Odeslání tržby přes Evidka API – PHP 7.4+ s rozšířením curl.
$base = getenv('EVIDKA_URL') ?: 'https://evidka.cz';
$key = getenv('EVIDKA_KEY') ?: die("Nastavte EVIDKA_KEY (zkušební klíč: curl -X POST $base/v1/sandbox)\n");
function evidovat(string $base, string $key, string $doklad, string $castka, string $platba): array
{
$ch = curl_init("$base/v1/trzby");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $key", 'Content-Type: application/json', "Idempotency-Key: $doklad"],
CURLOPT_POSTFIELDS => json_encode(['idJednotky' => 303, 'idPokl' => 'RECEPCE-1', 'castka' => $castka, 'zpusobPlatby' => $platba]),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$t = json_decode($body, true);
if ($status === 201 || $status === 200 || $status === 202) {
// 202 = ve frontě, POK dorazí webhookem
return ['stav' => $t['stav'], 'pok' => $t['pok'] ?? null, 'poradCis' => $t['poradCis']];
}
throw new RuntimeException("$status: " . ($t['chyba']['zprava'] ?? $body));
}
print_r(evidovat($base, $key, 'DOKLAD-' . time(), '349.00', 'karta'));
// Odeslání tržby přes Evidka API – C# / .NET 6+ (System.Net.Http.Json).
using System.Net;
using System.Net.Http.Json;
var baseUrl = Environment.GetEnvironmentVariable("EVIDKA_URL") ?? "https://evidka.cz";
var key = Environment.GetEnvironmentVariable("EVIDKA_KEY")
?? throw new InvalidOperationException("Nastavte EVIDKA_KEY (zkušební klíč: POST /v1/sandbox)");
using var http = new HttpClient { BaseAddress = new Uri(baseUrl), Timeout = TimeSpan.FromSeconds(15) };
http.DefaultRequestHeaders.Authorization = new("Bearer", key);
async Task<Trzba> Evidovat(string doklad, string castka, string platba)
{
using var req = new HttpRequestMessage(HttpMethod.Post, "/v1/trzby")
{
Content = JsonContent.Create(new { idJednotky = 303, idPokl = "RECEPCE-1", castka, zpusobPlatby = platba }),
};
req.Headers.Add("Idempotency-Key", doklad);
using var res = await http.SendAsync(req);
if (res.StatusCode is HttpStatusCode.Created or HttpStatusCode.OK or HttpStatusCode.Accepted)
return (await res.Content.ReadFromJsonAsync<Trzba>())!; // 202 = ve frontě, POK dorazí webhookem
throw new Exception($"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
}
var t = await Evidovat($"DOKLAD-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}", "349.00", "karta");
Console.WriteLine($"{t.stav} POK={t.pok} č.={t.poradCis}");
record Trzba(string id, string stav, string? pok, string poradCis, string castka);
"""Odeslání tržby přes Evidka API – Python 3.8+, jen standardní knihovna."""
import json
import os
import time
import urllib.error
import urllib.request
BASE = os.environ.get("EVIDKA_URL", "https://evidka.cz")
KEY = os.environ["EVIDKA_KEY"] # zkušební klíč: tlačítko „Vyzkoušet hned“ na https://evidka.cz
def evidovat(cislo_dokladu: str, castka: str, platba: str) -> dict:
data = json.dumps({"idJednotky": 303, "idPokl": "RECEPCE-1", "castka": castka, "zpusobPlatby": platba}).encode()
req = urllib.request.Request(
f"{BASE}/v1/trzby",
data=data,
method="POST",
headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json", "Idempotency-Key": cislo_dokladu},
)
try:
with urllib.request.urlopen(req, timeout=15) as res:
t = json.load(res)
# 201 = přijato (POK), 202 = ve frontě (POK dorazí webhookem), 200 = už evidováno dříve
return {"stav": t["stav"], "pok": t.get("pok"), "poradCis": t["poradCis"]}
except urllib.error.HTTPError as e:
chyba = json.load(e)
raise RuntimeError(f"{e.code}: {chyba.get('chyba', {}).get('zprava') or chyba}") from None
if __name__ == "__main__":
print(evidovat(f"DOKLAD-{int(time.time())}", "349.00", "karta"))
// Odeslání tržby přes Evidka API – Node.js 18+ (vestavěný fetch).
const BASE = process.env.EVIDKA_URL ?? 'https://evidka.cz';
const KEY = process.env.EVIDKA_KEY;
if (!KEY) throw new Error('Nastavte EVIDKA_KEY (zkušební klíč: curl -X POST ' + BASE + '/v1/sandbox)');
async function evidovat(doklad) {
const res = await fetch(`${BASE}/v1/trzby`, {
method: 'POST',
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': doklad.cislo },
body: JSON.stringify({ idJednotky: 303, idPokl: 'RECEPCE-1', castka: doklad.castka, zpusobPlatby: doklad.platba }),
});
const t = await res.json();
if (res.status === 201 || res.status === 200) return { ok: true, pok: t.pok, poradCis: t.poradCis, stav: t.stav };
if (res.status === 202) return { ok: true, pok: null, poradCis: t.poradCis, stav: 'fronta' }; // POK dorazí webhookem
throw new Error(`${res.status}: ${t.chyba?.zprava ?? t.chyba?.text ?? JSON.stringify(t)}`);
}
const vysledek = await evidovat({ cislo: `DOKLAD-${Date.now()}`, castka: '349.00', platba: 'karta' });
console.log(vysledek);
Klienta pro libovolný další jazyk vygenerujete z OpenAPI.
Autentizace
Každý požadavek (kromě GET /v1/zdravi) nese hlavičku Authorization: Bearer evk_live_…. Klíč patří k účtu a vidí jen jeho certifikáty a tržby. Klíče ukládáme jen jako hash – ztracený klíč nejde zobrazit, jen vytvořit nový (API klíče). Pro každou pokladnu nebo integraci doporučujeme samostatný klíč.
Prostředí a certifikáty
Prostředí se řídí certifikátem, kterým se tržba podepíše:
| Certifikát | Kam se posílá | Platnost POK |
|---|---|---|
| Testovací (playground, vydává „playground EETv2 NCA SubCA“) | pg.trzbyeet.gov.cz | neplatný, končí -ff |
| Produkční z DIS+ (CA EET) | trzbyeet.gov.cz | platný POK podle zákona |
Pokud má účet certifikáty pro víc EIČ, uveďte v tržbě eic nebo certifikatId. Z více platných certifikátů téhož EIČ se použije ten s nejdelší platností.
Zaevidovat tržbu
POST/v1/trzby
| Pole | Typ | Popis |
|---|---|---|
idJednotky * | číslo | Číslo evidenční jednotky (provozovny, webu, vozidla) přidělené v DIS+. |
castka * | řetězec | číslo | Celková částka tržby v Kč. Záporná = vrácení / oprava. |
idPokl | řetězec ≤ 20 | Označení pokladního zařízení, unikátní v rámci provozovny. Výchozí EVIDKA. Povolené znaky 0-9 a-z A-Z . , : ; / # - _ a mezera. |
poradCis | řetězec ≤ 25 | Pořadové číslo (číslo účtenky). Když chybí, Evidka přidělí RRRR/n zvlášť pro každou pokladnu. |
datTrzby | ISO 8601 | Okamžik tržby, výchozí „teď“. Použijte, když posíláte z offline fronty. Nesmí být v budoucnosti. |
zpusobPlatby | výčet | hotove, karta, qr, stravenky, jine. Do EET se neposílá, slouží pro uzávěrky. |
urcenoCerpZuct | částka | Platby určené k pozdějšímu čerpání (zálohy, prodej dárkového poukazu). |
cerpZuct | částka | Platby, které jsou čerpáním dřívější platby (uplatnění poukazu, zálohy). |
eicPoverujiciho | řetězec | EIČ poplatníka, za kterého evidujete v pověření. |
povereniVicePopl | bool | Tržba plyne více poplatníkům (sdružení, spoluvlastnictví). |
eic / certifikatId | řetězec | Výběr certifikátu, pokud jich má účet víc. |
overeni | bool | Ověřovací mód: FS zkontroluje formát a podpis, ale tržbu neeviduje. Stav overeno. |
externiId | řetězec | Vaše ID (objednávka, doklad). Stejné ID = stejná tržba, nic se neodešle dvakrát. Totéž dělá hlavička Idempotency-Key. |
poznamka | řetězec ≤ 200 | Interní poznámka, do EET se neposílá. |
| HTTP | Význam |
|---|---|
201 | Tržba přijata (stav: prijato, máte POK), nebo ověřovací mód prošel (overeno). |
202 | FS neodpověděla v mezní době nebo vrátila dočasnou chybu. Tržba je ve frontě (fronta) a doposílá se. |
200 | Tržbu se stejným externiId (nebo stejnými údaji) už máme, vracíme ji beze změny. |
400 | Neplatná data (pole chyba.pole říká které). Nic se neodeslalo. |
409 | Účet nemá platný certifikát. |
422 | FS zprávu odmítla kritickou chybou (např. neplatný podpis). Stav chyba, po nápravě zopakujte. |
Stavy a fronta (48 h)
| Stav | Co znamená | Co dělat |
|---|---|---|
| prijato | FS tržbu přijala a vrátila POK. | Nic. |
| fronta | Čeká na doposlání. Pokusy po 10 s, 20 s, 30 s, 1 min, 2 min, 5 min, 10 min a pak každých 15 min. | Nic – výsledek přijde webhookem. Po 48 h je poLhute: true. |
| chyba | Kritická chyba (kód 2–8), automaticky se neopakuje. | Opravte příčinu a zavolejte POST/v1/trzby/{id}/opakovat |
| overeno | Ověřovací mód proběhl bez chyb. | Nic, tržba se neeviduje. |
Opakované zaslání má podle specifikace nové UUID zprávy, aktuální čas odeslání a příznak prvni_zaslani=false. Data tržby zůstávají stejná, takže je finanční správa spáruje (jednoznačný kód tržby).
Seznam a detail
GET/v1/trzby?od=2027-01-01&do=2027-01-31&stav=fronta&pokladna=KASA-1&eic=CZ12345678&limit=100
Filtry jsou nepovinné, data podle dne tržby (čas ČR). Maximum je 1000 záznamů. Parametr eic se hodí partnerům s mnoha klienty v jednom účtu (funguje i u /v1/souhrn a /v1/export.csv).
GET/v1/trzby/{id} – detail včetně pokusyDetail: každý pokus s UUID zprávy, výsledkem, kódem a transactionId (hlavička X-Global-Transaction-Id z FS, hodí se při řešení problémů s podporou FS).
Storno
POST/v1/trzby/{id}/storno { "castka": "50.00" } (nepovinné, výchozí je celá částka)
Podle § 8 zákona se storno eviduje jako nová záporná tržba. Stornovat jde jen tržbu ve stavu prijato nebo fronta (tržbu s chybou ani z ověřovacího módu finanční správa nemá, vrátí se 409 / 400) a všechna storna dohromady nejvýš do částky původní tržby. Volání je idempotentní – opakované storno téže částky vrátí totéž storno (200).
Souhrn a CSV
GET/v1/souhrn?od=2027-01-01&do=2027-01-31 – součty přijatých tržeb po dnech, podle způsobu platby a podle pokladen. Ověřovací zprávy se nepočítají.
GET/v1/export.csv?od=…&do=… – CSV se středníkem a desetinnou čárkou (rovnou do Excelu nebo pro účetní).
Stav EET
GET/v1/stav (bez klíče) – dostupnost EET 2.0 podle monitoringu ověřovacími zprávami: poslední měření, dostupnost za 24 h a 7 dní, průměrná odezva, hodinové agregace. Používá ho stránka /stav/.
Certifikáty
GET/v1/certifikaty · POST/v1/certifikaty · DELETE/v1/certifikaty/{id}
POST /v1/certifikaty
{ "p12Base64": "MIIW…", "heslo": "…", "nazev": "Pobočka Brno", "prostredi": "produkce" }
POST/v1/certifikaty/{id}/obnovit – požádá API Certifikační autority EET o následný certifikát (JWT podepsaný stávajícím, ještě platným certifikátem), stáhne nový .p12, uloží ho šifrovaně a potvrdí převzetí. Starý certifikát dobíhá do konce platnosti, nové tržby se podepisují nástupcem. Certifikáty před vypršením hlídáme (dashboard je ukazuje 30 dní předem) a automatickou obnovu 21 dní před koncem platnosti vám zapneme na požádání. U zkušebního účtu se sdílený testovací certifikát neobnovuje (403).
Odpověď obsahuje EIČ z certifikátu, platnost a dniDoExpirace. Soubor i heslo ukládáme šifrovaně a nikdy je nevracíme. DELETE certifikát jen deaktivuje. Při podezření na zneužití ho zneplatněte i v DIS+.
API klíče
POST/v1/klice { "popis": "Pokladna bar" } → { "klic": "evk_live_…" } (zobrazí se jen jednou).
GET/v1/ucet – název účtu, tarif a přehled: fronta, chyby, tržby po lhůtě, certifikáty před expirací.
GET/v1/vyuziti?mesic=2027-01 – podklad pro fakturaci: počet aktivních pokladen (aspoň jedna přijatá tržba v měsíci) a tržeb podle EIČ.
Partneři: pod-účty pro vaše zákazníky
Partnerský účet (tarif Partner) zakládá pod-účet pro každého svého zákazníka. Každý pod-účet má vlastní certifikáty, tržby, dashboard a klíč, takže data zákazníků jsou oddělená.
POST /v1/partner/ucty { "nazev": "Salon Krása", "email": "salon@example.cz" }
→ 201 { "id": "ten_…", "klic": "evk_live_…" } # klíč pro pokladnu/dashboard zákazníka
# Partner jedná za zákazníka svým klíčem + hlavičkou:
POST /v1/certifikaty Evidka-Ucet: ten_… { "p12Base64": "…", "heslo": "…" }
POST /v1/trzby Evidka-Ucet: ten_… { "idJednotky": 303, "castka": "890.00" }
GET /v1/partner/ucty?mesic=2027-01 # přehled zákazníků: aktivní pokladny, tržby, fronta, chyby
Jednodušší varianta bez pod-účtů: nahrát certifikáty všech zákazníků do jednoho účtu a v tržbě posílat "eic": "CZ…". Souhrny, exporty i využití jdou filtrovat podle EIČ.
Webhooky
PUT/v1/webhook { "url": "https://…" } → vrátí secret. Hodnota null webhook vypne.
Události: trzba.prijata (i z fronty), trzba.chyba, trzba.po_lhute, certifikat.obnoven, certifikat.obnova_selhala. Tělo (u tržeb):
{ "udalost": "trzba.prijata", "cas": "2027-01-07T09:15:31.120Z", "trzba": { …stejný objekt jako v API… } }
Ověření podpisu: X-Evidka-Signature: sha256=HMAC_SHA256(secret, tělo požadavku). Odpovězte stavem 2xx. Když váš server neodpoví nebo vrátí jiný stav, webhook zkusíme znovu po 1 min, 5 min, 30 min, 2 h a 6 h. Přesměrování nenásledujeme a adresy uvnitř sítě (localhost, 10.x, 192.168.x…) odmítáme. Stav tržby si můžete vždy ověřit i přes GET /v1/trzby/{id}.
import { createHmac, timingSafeEqual } from 'node:crypto';
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-evidka-signature']));
Zkušební účet a simulace výpadku
POST/v1/sandbox (bez klíče) – založí zkušební účet na 7 dní s veřejným testovacím certifikátem finanční správy (fiktivní firma). Vrátí klic a odkazy na pokladnu a dashboard. Limity: 3 účty za hodinu z jedné adresy, 200 tržeb za 24 hodin. Totéž dělá tlačítko „Vyzkoušet hned“ na webu.
POST/v1/sandbox/vypadek { "minut": 2 } (1–10) – simuluje výpadek spojení s finanční správou. Nové tržby dostanou 202 a stav fronta, FS se mezitím nekontaktuje. Po skončení výpadku je brána sama doposílá (nové UUID, prvni_zaslani=false) a přijde webhook trzba.prijata. DELETE/v1/sandbox/vypadek výpadek ukončí hned. Probíhající výpadek ukazuje vypadekDo v GET /v1/ucet. Jen pro zkušební účty, jinak 403.
curl -X POST https://evidka.cz/v1/sandbox/vypadek -H "Authorization: Bearer evk_live_…" -d '{"minut":2}'
→ 200 { "vypadekDo": "2027-01-07T09:17:31.000Z" }
curl -X POST https://evidka.cz/v1/trzby -H "Authorization: Bearer evk_live_…" -d '{"idJednotky":303,"castka":"149.00"}'
→ 202 { "stav": "fronta", … } # po skončení výpadku → "prijato" + POK
Chyby
Chyby API mají tvar { "chyba": { "kod": "validace", "zprava": "idPokl: …", "pole": "idPokl" } } (pole má stejný název jako v požadavku). Limit je 50 požadavků za sekundu na účet (krátkodobě až 100), při překročení přijde 429 s hlavičkou Retry-After. Chyby z EET jsou v objektu tržby (chyba.kod podle specifikace FS):
| Kód | Význam | Opakuje se |
|---|---|---|
| -1 | Dočasná technická chyba na straně FS | ano, automaticky |
| 2 | Neplatné kódování XML | ne |
| 3 | Zpráva nevyhověla XML schématu | ne |
| 4 | Neplatný podpis (certifikát, prostředí) | ne |
| 6 | Chybná struktura EIČ | ne |
| 7 | Zpráva je příliš velká (> 12 kB) | ne |
| 8 | Technická chyba nebo chyba dat | ne |
Varování (tržba přijata, ale něco nesedí) jsou v poli varovani, například 1 = EIČ v tržbě se neshoduje s certifikátem nebo 6 = číslo provozovny nemá tvar čísla z DIS+.
Knihovna @evidka/eet2
Chcete posílat přímo, bez Evidka brány? Jádro brány je knihovna pro TypeScript (licence MIT), běží v Node.js 20+ i v prohlížeči. Finanční správa podporuje volání z prohlížeče (CORS). Zdrojový kód zatím posíláme na vyžádání, zveřejnění na npm připravujeme.
import { EetClient, signerFromP12 } from '@evidka/eet2';
const signer = await signerFromP12(p12Bytes, 'heslo');
const eet = new EetClient({ signer, prostredi: 'produkce' });
const r = await eet.odeslat({ eicPopl: 'CZ12345678', idJednotky: 303, idPokl: 'KASA-1', poradCis: '2027/15', celkTrzba: '349.00' });
if (r.stav === 'prijato') console.log(r.pok);
else if (r.stav === 'nedoruceno' || (r.stav === 'chyba' && r.opakovat)) queueForResend(r.zprava.etrzba); // eet.odeslatZnovu(etrzba)