Chyby a jak je číst
Chybová odpověď API se pozná podle stavového kódu, ne podle těla: podle toho,
co chybu vyvolalo, přijde mapa polí, objekt s kódem chyby, holý řetězec — nebo
vůbec nic. I chybová odpověď s tělem má Content-Type: application/hal+json,
takže podle hlavičky se úspěch od chyby poznat nedá.
Stavové kódy
Přehled kódů, které API vrací.
| Kód | Význam |
|---|---|
| 200 | OK – požadavek úspěšně zpracován |
| 201 | Created – záznam vytvořen |
| 204 | No Content – bez obsahu |
| 400 | Bad Request – špatný formát nebo datová chyba |
| 401 | Unauthorized – chybí nebo neplatí API klíč |
| 402 | Payment Required – zdroj není v tarifu, nebo je vyčerpaná kvóta dotazů |
| 403 | Forbidden – operaci nelze provést |
| 404 | Not Found – záznam nenalezen |
| 405 | Method Not Allowed – metoda není podporována |
| 413 | Payload Too Large – nahrávaný soubor je nad limit |
| 415 | Unsupported Media Type – nepodporovaný typ souboru |
| 500 | Internal Server Error – neočekávaná chyba na straně iÚčta |
| 501 | Not Implemented – zdroj tuto operaci neumí |
Kódy 413 a 415 vrací jen nahrávání příloh, viz
Přílohy. 405 vrací API na metody HEAD a PATCH, které
nepodporuje.
Kód 429 Too Many Requests API nevrací nikdy — vyčerpaná kvóta dotazů se
hlásí jako 402, viz Vyčerpaná kvóta.
Tvary chybového těla
Který tvar přijde, závisí na tom, kde v aplikaci požadavek skončil — ne na
stavovém kódu. Stejný kód 400 může přijít s několika různými těly.
| Tvar | Kdy vzniká |
|---|---|
{"errors": {"pole": "hláška"}} | neprošla validace vstupu |
{"error_code": "…", "message": "…"} | zamítnutý požadavek se strojově čitelným kódem |
"Text hlášky" (holý JSON řetězec) | ostatní zamítnutí |
{"pole": ["hláška"]} (mapa bez obálky errors) | validace parametrů seznamů a upload souborů, například {"date_from": ["Datum není platné."]}; vždy s kódem 400 |
["hláška"] (holé pole hlášek) | validace akcí a parametrů stránkování, například ["Doklad je smazaný, nebo účetní období, či období DPH je uzavřeno."] nebo ["Maximální počet záznamů na stránku je '200'"] po ?pageSize=500; vždy s kódem 400 |
Schéma Error ve specifikaci pokrývá právě těchto pět
tvarů.
Validace vstupu
Vzniká, když neprojde validace polí při vytváření nebo úpravě záznamu. Klíče mapy jsou názvy polí tak, jak je posíláte v požadavku.
Hláška se posílá jako holý řetězec, když je jediná, a jako pole až od dvou
výš. Validace jednoho pole se navíc po první chybě přeruší, takže řetězec je
běžný případ a pole výjimka. Kód, který rovnou iteruje
(foreach ($body['errors'][$pole] as $msg)), spadne na většině odpovědí —
ošetřete oba typy.
curl -i -X POST https://online.iucto.cz/api/1.3/invoice_issued \
-H "X-Auth-Key: $IUCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"customer_id": 1}'
{
"errors": {
"currency": "Měna není platná.",
"items": "Doklad musí mít alespoň jednu položku."
}
}
Chyba se strojově čitelným kódem
Objekt s error_code a message. Jediný kód, který dnes API vrací, je
TARIFF_MODULE_ACCESS_DENIED — dostane ho externí modul (propojení s bankou),
jehož firma nemá tarif zahrnující daný modul.
{
"error_code": "TARIFF_MODULE_ACCESS_DENIED",
"message": "Modul není dostupný, firma nemá aktivní tarif zahrnující tento modul."
}
Holý řetězec
Nejčastější tvar u zamítnutých požadavků. Tělo je platný JSON, ale je to
řetězec, ne objekt — json_decode vrátí string.
curl -i -X POST https://online.iucto.cz/api/1.3/invoice_issued \
-H "X-Auth-Key: $IUCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{'
"Neplatný JSON formát."
Jak poznat, který přišel
Rozlišuje se po dekódování JSON, podle typu a podle přítomnosti klíčů. Pořadí
podmínek je důležité — errors se kontroluje dřív než error_code.
$http = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$body = json_decode($response, true);
if ($http < 400) {
// úspěch
} elseif ($body === null) {
// prázdné tělo — kód je jediná informace
$detail = null;
} elseif (is_string($body)) {
// holá hláška
$detail = $body;
} elseif (isset($body['errors'])) {
// validace: pole => hláška, nebo pole => seznam hlášek
$detail = array_map(
static fn ($msgs) => is_array($msgs) ? $msgs : [$msgs],
$body['errors'],
);
} elseif (isset($body['error_code'])) {
// strojově čitelný kód
$detail = $body['error_code'];
} else {
// mapa bez obálky `errors` nebo holé pole hlášek
$detail = $body;
}
Odpovědi 401 a 404 tělo většinou nemají vůbec — server pošle jen
hlavičky a stavový kód. json_decode('') vrátí null, na což musí být kód
připravený. Prázdné tělo vrací i 204 No Content u úspěšného DELETE.
402 vs. 403
Oba kódy znamenají „požadavek je v pořádku, ale neprovede se". Liší se důvodem a hlavně tím, co s tím může udělat integrace.
| Kód | Význam | Co s tím |
|---|---|---|
402 | Zdroj není v tarifu firmy, nebo je vyčerpaná kvóta dotazů | Opakování nepomůže — do změny tarifu nebo do resetu kvóty bude odpověď stejná |
403 | Operaci nelze provést — uzavřené období, vazba na jiný záznam, EET, chybějící oprávnění uživatele | Musí se změnit stav dat nebo nastavení, ne požadavek |
Zkráceně: 403 znamená „tohle nejde udělat", 402 „tohle nemáte zaplacené".
Časté příčiny 402
| Situace | Tělo odpovědi |
|---|---|
| Zdroj vyžaduje vyšší tarif — sklady, skladové karty a skladové pohyby vyžadují tarif se skladovým modulem | "Zdroj je dostupný pouze ve vyším tarifu." |
| Vyčerpaná kvóta dotazů, viz Limity a kvóty | "Překročen limit na počet dotazů v API, zkontrolujte aktivní tarif. Dnešní využití: …, za měsíc: …" |
| Vytvoření, editace nebo zaúčtování dokladu při vyčerpaném limitu dokladů zkušebního tarifu | "Využíváte verzi zdarma s limitem … dokladů. Chcete-li pokračovat, zvolte některou z placených variant provozu iÚčta." |
| Externí modul bez odpovídajícího tarifu | objekt s error_code: TARIFF_MODULE_ACCESS_DENIED |
Časté příčiny 403
| Situace | Hláška | Co s tím |
|---|---|---|
| Účetní období nebo období DPH je uzavřeno | Doklad je smazaný, nebo účetní období, či období DPH je uzavřeno. | Buď období otevřít, nebo účtovat do aktuálního. Pozor na rozdíl u zaúčtování, viz níž |
| Záznam je navázaný na jiný | Nelze smazat záznam. | Zákazník na faktuře, faktura na platbě — nejdřív odstranit vazbu |
| Doklad má EET | Nelze upravit doklad, který byl odeslán do EET… | Doklady odeslané do EET nejde editovat ani mazat, řeší se opravným daňovým dokladem |
| Uživatel nemá právo | — | Oprávnění v API kopírují oprávnění ve webovém rozhraní, viz Autentizace |
Uzavřené období: 403 nebo 400
Uzavřené účetní období a uzavřené období DPH jsou dva nezávislé zámky a podle
operace vrací různý kód i různý tvar těla — 403 u editace a mazání, 400
u zaúčtování, přičemž text hlášky je v obou případech stejný. Celý výklad
včetně tabulky operací je v
Dva nezávislé zámky.
Větvěte podle kódu, ne podle textu
Rozhodovací logika patří na stavový kód. Texty hlášek jsou české, nejsou součástí smlouvy API a mění se s aplikací — porovnávání řetězců je proto křehké a rozbije se bez varování.
- Stavový kód je stabilní a strojově čitelný. Větvěte podle něj.
error_codeje jediný strojově čitelný identifikátor v těle. Dnes má jedinou hodnotu,TARIFF_MODULE_ACCESS_DENIED.- Text hlášky patří do logu a do zprávy pro člověka, ne do podmínky.
- Klíče mapy
errorsjsou názvy polí požadavku. Ty se dají použít k namapování chyby na konkrétní vstup ve vaší aplikaci. Nespoléhejte se ale na to, že klíčerrorsu kódu400vždy existuje — tvarů je pět.
Časté hlášky 400 a jejich příčina
Hlášky ke kódům 402 a 403 jsou v Časté příčiny 402
a Časté příčiny 403 výše.
| Kód | Hláška | Příčina |
|---|---|---|
| 400 | Komunikace musí probíhat přes protokol HTTPS. | Požadavek šel na http:// |
| 400 | Neplatná verze API, nebo zdroj. | Chybná verze nebo název zdroje v URL |
| 400 | Tělo požadavku je prázdné. | POST nebo PUT bez těla |
| 400 | Neplatný JSON formát. | Tělo se nepodařilo naparsovat |
| 400 | Neplatný typ požadavku. | multipart/form-data na zdroji, který nahrávání souborů neumí |
| 400 | Parametr doctype je povinný. / není platný. | Chybí nebo je neplatný doctype u typů DPH a typů účetních položek |
| 400 | Parametr date je povinný. / není platný. | Chybí nebo je neplatné date u sazeb DPH |
| 400 | Seznam sazeb DPH je dostupný pouze pro země EU | Dotaz na sazby DPH pro zemi mimo EU |
| 400 | Zaúčtovat lze pouze doklady z letošního roku… | Zkušební tarif, zaúčtování dokladu z minulého roku |
| 400 | Doklad je smazaný, nebo účetní období, či období DPH je uzavřeno. | Zaúčtování dokladu v uzavřeném období (jako holé pole hlášek) |
Kam dál
- Limity a kvóty — kolik dotazů má který tarif a jak je šetřit
- Řešení potíží — kontrolní seznamy k častým chybám integrace
- Autentizace — API klíč a oprávnění uživatele