API v1 OpenAPI Dashboard

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.

Rychlý start

  1. 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+.
  2. 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átKam se posíláPlatnost POK
Testovací (playground, vydává „playground EETv2 NCA SubCA“)pg.trzbyeet.gov.czneplatný, končí -ff
Produkční z DIS+ (CA EET)trzbyeet.gov.czplatný 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

PoleTypPopis
idJednotky *čísloČíslo evidenční jednotky (provozovny, webu, vozidla) přidělené v DIS+.
castka *řetězec | čísloCelková částka tržby v Kč. Záporná = vrácení / oprava.
idPoklřetězec ≤ 20Označ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 ≤ 25Pořadové číslo (číslo účtenky). Když chybí, Evidka přidělí RRRR/n zvlášť pro každou pokladnu.
datTrzbyISO 8601Okamžik tržby, výchozí „teď“. Použijte, když posíláte z offline fronty. Nesmí být v budoucnosti.
zpusobPlatbyvýčethotove, karta, qr, stravenky, jine. Do EET se neposílá, slouží pro uzávěrky.
urcenoCerpZuctčástkaPlatby určené k pozdějšímu čerpání (zálohy, prodej dárkového poukazu).
cerpZuctčástkaPlatby, které jsou čerpáním dřívější platby (uplatnění poukazu, zálohy).
eicPoverujicihořetězecEIČ poplatníka, za kterého evidujete v pověření.
povereniVicePoplboolTržba plyne více poplatníkům (sdružení, spoluvlastnictví).
eic / certifikatIdřetězecVýběr certifikátu, pokud jich má účet víc.
overeniboolOvěřovací mód: FS zkontroluje formát a podpis, ale tržbu neeviduje. Stav overeno.
externiIdřetězecVaš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 ≤ 200Interní poznámka, do EET se neposílá.
HTTPVýznam
201Tržba přijata (stav: prijato, máte POK), nebo ověřovací mód prošel (overeno).
202FS neodpověděla v mezní době nebo vrátila dočasnou chybu. Tržba je ve frontě (fronta) a doposílá se.
200Tržbu se stejným externiId (nebo stejnými údaji) už máme, vracíme ji beze změny.
400Neplatná data (pole chyba.pole říká které). Nic se neodeslalo.
409Účet nemá platný certifikát.
422FS zprávu odmítla kritickou chybou (např. neplatný podpis). Stav chyba, po nápravě zopakujte.

Stavy a fronta (48 h)

StavCo znamenáCo dělat
prijatoFS 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.
chybaKritická chyba (kód 2–8), automaticky se neopakuje.Opravte příčinu a zavolejte POST/v1/trzby/{id}/opakovat
overenoOvěř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ódVýznamOpakuje se
-1Dočasná technická chyba na straně FSano, automaticky
2Neplatné kódování XMLne
3Zpráva nevyhověla XML schématune
4Neplatný podpis (certifikát, prostředí)ne
6Chybná struktura EIČne
7Zpráva je příliš velká (> 12 kB)ne
8Technická chyba nebo chyba datne

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)