Přeskočit na hlavní obsah

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ódVýznam
200OK – požadavek úspěšně zpracován
201Created – záznam vytvořen
204No Content – bez obsahu
400Bad Request – špatný formát nebo datová chyba
401Unauthorized – chybí nebo neplatí API klíč
402Payment Required – zdroj není v tarifu, nebo je vyčerpaná kvóta dotazů
403Forbidden – operaci nelze provést
404Not Found – záznam nenalezen
405Method Not Allowed – metoda není podporována
413Payload Too Large – nahrávaný soubor je nad limit
415Unsupported Media Type – nepodporovaný typ souboru
500Internal Server Error – neočekávaná chyba na straně iÚčta
501Not 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.

TvarKdy 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.

Hodnota je řetězec, ne vždy pole

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;
}
Prázdné tělo

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ódVýznamCo s tím
402Zdroj 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á
403Operaci nelze provést — uzavřené období, vazba na jiný záznam, EET, chybějící oprávnění uživateleMusí 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

SituaceTě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 tarifuobjekt s error_code: TARIFF_MODULE_ACCESS_DENIED

Časté příčiny 403

SituaceHláškaCo s tím
Účetní období nebo období DPH je uzavřenoDoklad 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á EETNelze 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ávoOprá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_code je 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 errors jsou 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íč errors u kódu 400 vž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ódHláškaPříčina
400Komunikace musí probíhat přes protokol HTTPS.Požadavek šel na http://
400Neplatná verze API, nebo zdroj.Chybná verze nebo název zdroje v URL
400Tělo požadavku je prázdné.POST nebo PUT bez těla
400Neplatný JSON formát.Tělo se nepodařilo naparsovat
400Neplatný typ požadavku.multipart/form-data na zdroji, který nahrávání souborů neumí
400Parametr doctype je povinný. / není platný.Chybí nebo je neplatný doctype u typů DPH a typů účetních položek
400Parametr date je povinný. / není platný.Chybí nebo je neplatné date u sazeb DPH
400Seznam sazeb DPH je dostupný pouze pro země EUDotaz na sazby DPH pro zemi mimo EU
400Zaúčtovat lze pouze doklady z letošního roku…Zkušební tarif, zaúčtování dokladu z minulého roku
400Doklad 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