Pre vývojárov
Dokumentácia API
REST API pre slovenskú povinnú e-fakturáciu. Vystavíte doklad, dostanete ho v EN 16931 / Peppol BIS 3.0, doručíme ho odberateľovi aj Finančnej správe a dáme vedieť webhookom.
Základná adresa http://127.0.0.1:3100 · verzia 0.1.0 · úplná referencia (Swagger) · OpenAPI JSON
Rýchly štart
Začnite testovacím kľúčom. Vydáva sa v administrácii po registrácii a je zadarmo — sandbox sa neúčtuje, lebo účtovať niekomu, že skúša, je najspoľahlivejší spôsob, ako ho odnaučiť skúšať. Doklady z neho sa nikdy neodošlú do Peppolu ani Finančnej správe, majú vlastný číselný rad s predponou TEST- a nevstupujú do DPH prehľadov.
# 1. Vystavte doklad. Idempotency-Key je vaše číslo objednávky.
curl -X POST http://127.0.0.1:3100/v1/documents \
-H "Authorization: Bearer kvi_test_..." \
-H "Idempotency-Key: objednavka-1042" \
-H "content-type: application/json" \
-d '{
"issueDate": "2027-03-14",
"taxPointDate": "2027-03-14",
"dueDate": "2027-03-28",
"buyer": {
"name": "Odberateľ s.r.o.",
"vatNumber": "SK2020317068",
"address": { "street": "Hlavná 1", "city": "Bratislava",
"postalCode": "81101", "countryCode": "SK" }
},
"lines": [{
"name": "Konzultácie", "quantity": "8", "unitCode": "HUR",
"netUnitPrice": "75.00", "vat": { "category": "S", "rate": "23" }
}]
}'
# 2. Vystavenie mu pridelí číslo. Až tu doklad prestáva byť meniteľný.
curl -X POST http://127.0.0.1:3100/v1/documents/{id}/issue \
-H "Authorization: Bearer kvi_test_..." \
-H "Idempotency-Key: objednavka-1042-issue""75.00", nie 75.00. JSON number je IEEE 754 double a nevie presne vyjadriť peňažnú sumu — neposielame ich tak ani my, ani vy.Tri veci, ktoré si v ručne písanom klientovi treba ustrážiť: idempotency kľúč musí prežiť opakovanie, vetviť sa treba podľa stabilného kódu chyby a nie podľa jej textu, a podpis webhooku sa overuje nad surovým telom. Presne to za vás urobia knižnice nižšie, keď vyjdú.
Knižnice a pluginy
Každá z nich existuje preto, že v ručne písaných klientoch sa opakovali tie isté tri chyby: chýbajúci idempotency kľúč pri opakovaní, vetvenie podľa textu chyby namiesto kódu, a overovanie podpisu webhooku nad znovuzloženým JSON-om namiesto surového tela.
npm, Packagist ani PyPI ich ešte nepoznajú — preto tu nenájdete inštalačný príkaz. Radšej nič, než príkaz, ktorý vám vráti 404. Dovtedy sa integruje priamo cez REST API vyššie; kto chce balík skôr, nech nám napíše.@kvitos/sdkpripravujemekvitos/sdkpripravujemekvitos/laravelpripravujemekvitospripravujeme@kvitos/mcppripravujemeWooCommercepripravujemePrestaShoppripravujemeKonektor na Shoptet máme napísaný, doplnok v ich katalógu ešte nie. Keď ktorákoľvek z týchto vecí vyjde, objaví sa v zozname zmien — nie v tichosti.
Kľúče a rozsahy
Authorization: Bearer kvi_… pre ostrý kľúč, kvi_test_… pre sandbox. Kľúč vidí len to, na čo bol vydaný; pri chýbajúcom rozsahu príde 403 insufficient_scope a v správe je uvedené, ktorý rozsah chýba.
Povolený je konkrétny rozsah, skupinový zástupný znak (documents:*) aj *. Pre e-shop zvyčajne stačí documents:*, customers:* a lookup:read.
| Rozsah | Čo umožňuje |
|---|---|
documents:read | Čítanie dokladov, PDF, UBL, príloh. |
documents:write | Vytvorenie a vystavenie dokladu, odoslanie, upomienka. |
customers:read / customers:write | Odberatelia. |
payments:read / payments:write | Platby, bankové pohyby, import výpisu. |
expenses:read / expenses:write | Náklady a prijaté doklady. |
inbound:read / inbound:write | Prijaté doklady z Peppolu. |
catalog:read / catalog:write | Cenník. |
commercial:read / commercial:write | Ponuky, objednávky, dodacie listy. |
recurring:read / recurring:write | Pravidelná fakturácia. |
reports:read | Štatistiky, DPH prehľady, audit log. |
exports:read / exports:write | Účtovné exporty a odoslanie do účtovníctva. |
webhooks:manage | Správa endpointov a log doručení. |
settings:read / settings:manage | Nastavenia subjektu a šablóny. |
users:manage | Používatelia a API kľúče. |
lookup:read | Hľadanie firmy v registroch RPO, RÚZ, VIES a ARES a overenie IČ DPH. |
jobs:run | Spustenie naplánovaných úloh. |
AI asistent (MCP)
@kvitos/mcp pripojí vášho asistenta — Claude, Cursor — k vašim dokladom. Beží na vašom počítači s vaším kľúčom; nič z toho nehostíme. Odpovedá na otázky, ktoré si aj tak kladiete: kto mi dlhuje, prečo tá faktúra neodišla, nie je tento náklad zapísaný dvakrát.
Faktúru nevystaví a návrh, ktorý pripraví, potvrdzuje človek — kľúč to nedokáže, nech má akékoľvek rozsahy.
Opakovanie zápisov
Idempotency-Key.- Ten istý kľúč a to isté telo → uložená odpoveď, označená hlavičkou
Idempotency-Replayed: true. - Ten istý kľúč, iné telo →
422 idempotency_key_reused. To je znovupoužitý kľúč, nie opakovanie. - Ten istý kľúč, kým prvá požiadavka beží →
409, skúste o chvíľu. - Prvá požiadavka spadla na 5xx → neuloží sa nič, opakovanie je naozaj opakovanie.
- Uložený výsledok je k dispozícii 24 hodín.
Najlepší kľúč je niečo prirodzené — číslo objednávky. Vygenerovaný náhodný kľúč ochráni pred prerušeným spojením, nie pred vaším vlastným dvojitým volaním.
Chyby a limity
{
"error": {
"code": "insufficient_scope",
"message": "API kľúč nemá rozsah documents:write."
}
}Rozhodujte sa podľa code, nie podľa textu — text je slovenská veta pre človeka a môže sa zmeniť. Pri chybe validácie pribudne pole issues s konkrétnymi poľami.
| Kód | Stav | Význam |
|---|---|---|
unauthorized | 401 | Chýbajúci alebo neplatný kľúč. |
key_expired | 401 | Kľúču uplynula platnosť. |
insufficient_scope | 403 | Kľúč nemá potrebný rozsah — správa ho pomenuje. |
rate_limited | 429 | Prekročený limit rýchlosti. Počkajte podľa Retry-After. |
quota_exceeded | 402 | Vyčerpaný mesačný počet dokladov, alebo je účet pozastavený. |
shop_limit_reached | 402 | Plán zahŕňa menej e-shopov, než je pripojených. |
idempotency_key_reused | 422 | Ten istý kľúč s iným telom požiadavky. |
idempotency_in_progress | 409 | Prvá požiadavka ešte beží. Skúste o chvíľu. |
already_issued | 409 | Doklad už bol vystavený. |
document_invalid | 422 | Doklad neprešiel kontrolou; pole `issues` hovorí prečo. |
invalid_cursor | 400 | Cursor nepochádza z našej odpovede. |
Je to na našej strane? Aktuálny stav doručovania, hlásenia a fronty je na kvitos.sk/stav — bez prihlásenia, a strojovo na GET /v1/status.
Každá odpoveď nesie RateLimit-Limit, RateLimit-Remaining a RateLimit-Reset. Pri prekročení príde 429 s Retry-After v sekundách. Rýchlosť určuje plán zákazníka.
Mesačný počet dokladov je iná vec než rýchlosť: pri jeho vyčerpaní príde 402 quota_exceeded. Opakovanie nepomôže — pomôže vyšší plán alebo ďalšie obdobie. Aktuálny stav vráti GET /v1/subscription. Testovacie doklady sa do limitu nepočítajú, takže sandbox funguje aj vtedy, keď je ostrá kvóta vyčerpaná.
Webhooky
Udalosti: document.issued, document.paid, document.credited, payment.matched, payment.unmatched, delivery.delivered, delivery.failed, inbound.received, tax.reported, tax.rejected, ping.
import { parseWebhook } from "@kvitos/sdk";
app.post("/webhooks/kvitos",
express.raw({ type: "application/json" }), // surové telo!
(req, res) => {
const event = parseWebhook(
process.env.KVITOS_WEBHOOK_SECRET,
req.body.toString("utf8"),
req.get("kvitos-signature"),
);
if (event.event === "document.paid") {
// Uvoľnite objednávku.
}
res.sendStatus(200); // Odpovedzte rýchlo, prácu si zaraďte do fronty.
});Doručenie sa opakuje s odstupmi 30 s → 2 min → 10 min → 1 h → 6 h → 24 h. Hlavička kvitos-delivery je stále tá istá, takže podľa nej rozpoznáte opakovanie tej istej udalosti. Endpoint, ktorý dlhodobo neodpovedá, vypneme a napíšeme dôvod. Čo sme poslali a s akou odpoveďou vidíte v administrácii v logu doručení.
Zoznam udalostí
strojovo čitateľneTen istý číselník vracia GET /v1/webhooks/events aj s ukážkovým telom každej udalosti — vrátane obálky id, event a occurredAt. Nástroj ako Zapier či Make si z neho vyskladá zoznam spúšťačov a polia na mapovanie. Prihlásenie na to netreba.
| Udalosť | Čo znamená | Kedy vzniká |
|---|---|---|
document.issued | Doklad dostal číslo z radu a stal sa nemenným. | Pri vystavení dokladu, v tej istej transakcii. Za doklad, ktorého vystavenie zlyhalo, udalosť nevznikne. |
document.paid | Doklad je uhradený v plnej výške. | Po zaevidovaní úhrady, ktorá doklad doplatila do nuly. Čiastková úhrada vydá len `payment.matched`. |
document.credited (zatiaľ sa neodosiela) | K dokladu vznikol dobropis. | Zatiaľ nikdy — udalosť sa dá odoberať, ale aplikácia ju neodosiela. Na dobropis sa napojte cez `document.issued` s `kind: "credit_note"`. |
payment.matched | Platba sa priradila k dokladu. | Pri zaevidovaní úhrady — z banky, z platobnej brány aj ručne. Za jeden doklad môže prísť viackrát, ak sa platí po častiach. Ak platba prevýšila zvyšok dokladu, nesie `overpaidAmount` — o toľko odberateľ zaplatil viac. |
payment.unmatched | Prišli peniaze, ktoré sa nedali priradiť k žiadnemu dokladu. | Pri zaevidovaní platby, ktorú párovanie nevedelo umiestniť (`reason` hovorí prečo), a pri odpárovaní omylom priradenej platby — vtedy nesie `previousDocumentId` a `documentWasPaid`; ak bol doklad predtým uhradený, už nie je. |
delivery.delivered | Doklad bol doručený odberateľovi cez sieť Peppol. | Až keď doručenie potvrdí prístupový bod. Technické prevzatie do fronty udalosť nevydáva — bolo by to tvrdenie, ktoré ešte nikto nepotvrdil. |
delivery.failed | Doručenie dokladu zlyhalo. | Po vyčerpaní opakovaní, alebo keď prístupový bod ohlási trvalé zlyhanie. `permanent: true` znamená, že sa to samo už neskúsi. |
inbound.received | Prišiel doklad od dodávateľa. | Po uložení prijatého dokladu, pred potvrdením prevzatia prístupovému bodu. |
tax.reported (zatiaľ sa neodosiela) | Údaje o doklade prevzala Finančná správa. | Zatiaľ nikdy — udalosť sa dá odoberať, ale aplikácia ju neodosiela. Stav hlásenia je v `GET /v1/documents/:id`. |
tax.rejected (zatiaľ sa neodosiela) | Finančná správa údaje o doklade odmietla. | Zatiaľ nikdy — udalosť sa dá odoberať, ale aplikácia ju neodosiela. Odmietnuté hlásenia vidno v prevádzkovom prehľade a v upozorneniach. |
ping | Testovacia udalosť. Nevzťahuje sa na žiadny doklad. | Keď ju vyžiadate — tlačidlom v administrácii alebo cez `POST /v1/webhooks/:id/test`. |
Skúšobné doručenie si vypýtate cez POST /v1/webhooks/:id/test s telom {"event": "document.paid"}. Na rozdiel od ping ním overíte aj vetvenie podľa event a čítanie polí z data. Telo je ukážkové — identifikátory v ňom neukazujú na žiadny skutočný doklad.
Synchronizácia
Na priebežné dosynchronizovanie použite updatedSince — vráti, čo sa od daného okamihu zmenilo, od najstaršieho. Za zmenu sa počíta aj úhrada a doručenie: vystavený doklad sa už nemení, ale práve to, či je uhradený, sa spravidla pýtate.
GET http://127.0.0.1:3100/v1/documents?updatedSince=2027-03-01T00:00:00Z&limit=100
# Ďalšiu stránku berte cez nextCursor z odpovede, nie cez offset —
# cursor zostane správny, aj keď medzitým pribudnú nové doklady.
GET http://127.0.0.1:3100/v1/documents?updatedSince=...&cursor=eyJ...Filtre: status, kind, customerId, number, issuedFrom, issuedTo, mode.
Prehľad do tabuľky
GET /v1/exports/overview.csv vráti riadok na doklad: čísla, dátumy, odberateľa, sumy, uhradenú časť a zostatok. Nie je to dávka na import do účtovného programu — na to sú exporty — ale plochá tabuľka pre človeka, ktorý si nad ňou robí vlastné súčty.
# Bodkočiarka je predvolená kvôli Excelu so slovenským nastavením.
curl -H "Authorization: Bearer $KVITOS_API_KEY" \
"http://127.0.0.1:3100/v1/exports/overview.csv?from=2027-01-01&to=2027-12-31"
# Čiarka pre nástroj, ktorý bodkočiarku nečaká:
# ...&delimiter=comma
# Sandbox namiesto ostrých dokladov:
# ...&mode=testZostatok je stĺpec, nie vzorec. Sumy chodia ako text presne v tvare, v akom sú u nás — tabuľka si ich naformátuje sama a nič sa po ceste nezaokrúhli. Koncepty v prehľade nie sú.
Authorization k nej pridať nevie. Prehľad dnes stiahnete kľúčom — cez curl, cez scenár v Make alebo Zapieri, alebo tlačidlom v administrácii. Odkaz, ktorý si tabuľka obnoví sama, zatiaľ nevydávame: kľúč v adrese by skončil v histórii prehliadača aj v logoch.Platobné brány
POST /v1/documents/:id/checkout vytvorí platbu na faktúru a vráti adresu, kam pošlete platiteľa. Keď peniaze dorazia, brána nám pošle podpísané oznámenie, my zapíšeme úhradu a vydáme payment.matched, prípadne aj document.paid.
Brána sa nastavuje v administrácii v Nastaveniach: poskytovateľ a identifikátor vášho obchodníckeho účtu. Oznámenia o platbách chodia na POST /v1/webhooks/payments/:tenantId a každé sa overuje skôr, než sa z neho stane úhrada — neoverené doručenie zahadzujeme.
| Poskytovateľ | Ako to funguje |
|---|---|
| Comgate | Karta aj bankové tlačidlo. Platbu vytvárame pod vaším číslom obchodníka, oznámenie overujeme tajomstvom, ktoré Comgate v ňom posiela. Predautorizovanú platbu za uhradenú neoznačíme — peniaze ešte neprišli. |
| Iná brána | Všeobecný adaptér pre bránu, ktorá platbu vytvorí volaním REST API a vráti adresu, kam poslať platiteľa. Názvy polí sa dolaďujú na konkrétneho poskytovateľa; nastavuje ho prevádzkovateľ. |
| Dev | Testovacia brána pre vývoj. Prejde celú cestu vrátane podpísaného oznámenia, len peniazmi nepohne. V produkcii sa nespustí. |
Účtovníctvo
POST /v1/exports vráti dávku vo formáte, ktorý účtovník naimportuje: CSV s riadkom na každú sadzbu, KROS OMEGA a ALFA plus, POHODA, Money S3, ISDOC alebo UBL. Zoznam dostupných formátov vráti GET /v1/exports v poli adapters.
Formát označený verified: false je zostavený podľa zverejnenej štruktúry výrobcu, ale zatiaľ nebol overený importom do reálnej inštalácie. Pred prvým ostrým použitím ho nechajte skúsiť účtovníkovi na jednej dávke.
KROS Fakturácia má vlastné REST API — doklady doň vieme zapísať priamo, bez sťahovania súboru. Nastavuje sa v administrácii v Exportoch.
Verzie a zmeny
pred 1.0Čo môžeme zmeniť bez ohlásenia
- Nové endpointy a nové nepovinné polia v požiadavke.
- Nové polia v odpovedi — váš klient musí neznáme polia ignorovať, nie na nich spadnúť.
- Text chybovej správy. Je to slovenská veta pre človeka; rozhodujte sa podľa poľa code.
- Obsah a dĺžka cursora. Je nepriehľadný — posielajte späť presne to, čo ste dostali.
- Poradie polí v JSON-e.
Čo držíme
- Význam existujúceho poľa sa nezmení. Ak by sa mal zmeniť, pribudne nové pole.
- Existujúce pole z odpovede nezmizne bez novej verzie cesty.
- Chybový kód (pole code) si ponechá význam.
- Doklad, ktorý bol vystavený, sa už nikdy nezmení — ani cez API, ani inak.
- Číselný rad zostane bez medzier a bez duplicít.
Zmena, ktorá by rozbila existujúcu integráciu, znamená /v2 — a /v1 pri nej beží ďalej ešte 12 mesiacov. Integráciu e-shopu spravuje ten, kto ju písal, a to často nie je niekto, kto je na projekte stále.
Zastaraný nie je zatiaľ žiadny endpoint. Keď nejaký bude, objaví sa tu aj s označením zastarané v referencii — a bude odpovedať ďalej, kým neuplynie lehota.
| Verzia | Dátum | Zmena |
|---|---|---|
0.1.0 | 2026-09-28 | Úprava pravidelnej faktúry `PATCH /v1/recurring/{id}` mení názov, interval, ďalší termín, odberateľa, položku a bežnú cenu rozvrhu. Iná suma pre jeden beh ostáva výnimkou (`PUT /v1/recurring/{id}/overrides`) a týmto volaním sa neprepisuje. |
0.1.0 | 2026-09-25 | Obrat, hranica registrácie a povinnosti, ktoré z obratu nevznikajú `GET /v1/vat-threshold` vráti obrat za rok, vzdialenosť k hranici a mesiac, v ktorom by hranica padla pri doterajšom tempe. **Obrat počítame z dodaní, nie z platieb** (§ 4 ods. 7) — teda z vystavených dokladov podľa dátumu dodania; zálohová faktúra sa nezapočítava, dobropis obrat znižuje.
`GET /v1/vat-registration` prejde tohtoročné prijaté doklady a povie, z ktorých vznikla registrácia podľa § 7 (tovar z EÚ nad ročnú hranicu) alebo § 7a (služba z EÚ alebo do EÚ — bez akejkoľvek hranice). `POST /v1/expenses` prijíma `supplierCountry`, `supplierVatNumber` a `supplyType`; keď z dokladu povinnosť plynie, odpoveď nesie blok `registration`.
Firma môže mať tretie postavenie voči DPH: `registered_only` — osoba registrovaná podľa § 7 alebo § 7a, ktorá **nie je platiteľom**. Má IČ DPH a uvádza ho na faktúre, v tuzemsku daň neúčtuje a odpočet nemá; službu dodanú platiteľovi do EÚ vystavuje s prenesením daňovej povinnosti. |
0.1.0 | 2026-09-25 | Prílohy majú úložisko, zberný odkaz ich prijme aj bez účtu `POST /v1/attachments` ukladal súbor ako base64 priamo do stĺpca, s limitom 2 MB a bez kontroly obsahu. Odteraz idú bajty do archívu s tou istou desaťročnou lehotou ako vystavené XML, strop je 15 MB a prijímame PDF, fotku dokladu (JPG, PNG, HEIC, WEBP, TIFF) a faktúru v XML — obsah sa overuje proti ohlásenému typu, takže `.exe` premenované na `.pdf` neprejde. Cesta ani staré polia `contentBase64`, `documentId` a `expenseId` sa nemenia; nové sú `dataBase64`, `ownerKind` a `ownerId`.
Bez vlastníka skončí súbor v priečinku **Na spracovanie** (`GET /v1/attachments`), odkiaľ ho `POST /v1/attachments/{id}/link` zaradí k dokladu. `GET /v1/attachments/{id}/file` vráti bajty. `POST /v1/attachments/{id}/discard` prílohu vyradí zo zoznamu — z archívu nie, lebo je to súčasť účtovného záznamu.
`POST /v1/upload-links` vydá **zberný odkaz**: adresu, na ktorej nahrá doklad ktokoľvek bez účtu a bez hesla — kolega, šofér, klient účtovnej kancelárie. Odkaz má platnosť, strop počtu nahraní a dá sa zneplatniť; nahraté doklady padajú do priečinka Na spracovanie, nikdy priamo do denníka. |
0.1.0 | 2026-09-25 | Návrhy: asistent odporúča, potvrdzuje človek `POST /v1/suggestions` zapíše návrh (zaúčtovanie, zaradenie, duplicita) k dokladu, nákladu alebo prijatej faktúre. Návrh nič nemení. `GET /v1/suggestions` vráti tie, ktoré čakajú. Potvrdiť alebo zamietnuť ich smie **len prihlásený človek** — kľúču to API odmietne kódom `human_required`, nech má akékoľvek rozsahy, lebo za účtovný záznam zodpovedá osoba. Nové rozsahy `suggestions:read` a `suggestions:write`; `accounting:write` na navrhovanie netreba.
`GET /v1/expenses/{id}/posting-suggestion` povie, na ktorý účet náklad pravdepodobne patrí — z histórie firmy, teda z toho, kam boli zaúčtované predchádzajúce faktúry od toho istého dodávateľa. Bez histórie vráti predkontáciu firmy s `confidence: low` a povie, že je to predvoľba. |
0.1.0 | 2026-09-25 | Kontrola dokladu nanečisto, duplicity nákladov a rozsahy v `/v1/me` `GET /v1/documents/{id}/check` spustí pravidlá EN 16931 — a pri doklade do siete aj Peppol BIS 3.0 — bez vystavenia, takže chyby sa dajú nájsť skôr, než sa spáli číslo z radu. `GET /v1/expenses/duplicates` vráti prijaté faktúry, ktoré vyzerajú ako zapísané dvakrát: isté zhody podľa čísla a podozrenia podľa zhodnej sumy v odstupe pár dní. `GET /v1/me` navyše vracia blok `access` s rozsahmi a režimom kľúča — integrácia sa už nemusí pýtať 403-kou. |
0.1.0 | 2026-09-24 | Mapovanie platobných metód e-shopu Doklad môže niesť nepovinné pole `shopPaymentMethod` s pôvodným názvom platobnej metódy z e-shopu (`provider`: `woocommerce` alebo `prestashop`). Formu úhrady doplní Kvitos podľa toho, ako si ju firma namapovala, a prebije tým `payment.meansCode` z dokladu. Nenamapovaná metóda doklad neodmietne — vystaví sa tak, ako prišiel, a metóda sa zapamätá, aby ju firma videla a domapovala. |
0.1.0 | 2026-09-02 | Prvé rozhranie Doklady, odberatelia, platby, náklady, cenník, obchodné doklady, pravidelná fakturácia, prehľady a exporty. Kľúče s rozsahmi, limity rýchlosti, idempotentné zápisy, webhooky s podpisom, sandbox s vlastným číselným radom a synchronizácia cez `updatedSince` s cursorom. |
Ten istý zoznam strojovo: GET http://127.0.0.1:3100/v1/changelog — bez kľúča. Váš systém tak vie svojmu prevádzkovateľovi povedať, že sa niečo pohlo, bez toho, aby to niekto musel čítať u nás.