# Endpointy REST API

> Všechny cesty REST API i s tím, jaké oprávnění chtějí, co vracejí a čím se dají zúžit. Tabulka se generuje z kódu, který je obsluhuje.

- Sekce: Pro vývojáře
- Stránka: https://shopzi.cz/dokumentace/vyvojari/rest-api-endpointy
- Ověřeno: 2026-10-20

---

REST API má 24 cest a všechny odpovídají jen na `GET`. Tabulka
níž se **generuje z kódu**, který ty cesty obsluhuje, takže nemůže tvrdit nic, co by neplatilo pro
build, na kterém tenhle článek běží. Tentýž popis je strojově čitelný v souboru
`/openapi-v1.json`, viz článek [OpenAPI popis REST API](/dokumentace/vyvojari/openapi).

| Endpoint | Oprávnění | Obálka | Pole | Filtry |
| --- | --- | --- | --- | --- |
| `GET /api/v1/catalog-items` | `products:read` | Stránka v data[], kurzor v next_cursor | `id` `feed_id` `external_id` `group_id` `ean` `sku` `title` `description` `brand` `category_path[]` `images[]` `params{}` `cost_minor` `cost_includes_vat` `retail_price_minor` `min_price_minor` `currency_code` `vat_rate` `stock` `availability` `lead_days` `gone_at` `created_at` `updated_at` | `feed_id` `include_gone` `updated_since` |
| `GET /api/v1/categories` | `products:read` | Stránka v data[], kurzor v next_cursor | `id` `parent_id` `name` `slug` `full_path` `depth` `position` `is_active` `show_in_menu` `created_at` `updated_at` | `updated_since` |
| `GET /api/v1/customers` | `customers:read` | Stránka v data[], kurzor v next_cursor | `id` `type` `name` `company_name` `ico` `dic` `email` `phone` `billing_address{}` `delivery_address{}` `marketing_consent` `marketing_consent_at` `customer_group{}` `created_at` `updated_at` | `updated_since` |
| `GET /api/v1/customers/{id}` | `customers:read` | Jeden objekt v data | `id` `type` `name` `company_name` `ico` `dic` `email` `phone` `billing_address{}` `delivery_address{}` `marketing_consent` `marketing_consent_at` `customer_group{}` `created_at` `updated_at` | - |
| `GET /api/v1/documents` | `documents:read` | Stránka v data[], kurzor v next_cursor | `id` `doc_type` `doc_number` `order_id` `issued_at` `duzp` `total_minor` `currency_code` `tax_regime` `customer_ico` `customer_dic` `created_at` | `doc_type` `updated_since` |
| `GET /api/v1/documents/{id}` | `documents:read` | Jeden objekt v data | `id` `doc_type` `doc_number` `order_id` `order_number` `issued_at` `issue_date` `duzp` `due_date` `currency_code` `tax_regime` `is_vat_payer` `payment_method` `variable_symbol` `is_paid` `supplier{}` `customer{}` `lines[]` `shipping_gross_minor` `shipping_vat_rate` `discount_total_minor` `rounding_minor` `vat_breakdown[]` `total_base_minor` `total_vat_minor` `total_gross_minor` `original_doc_number` `correction_reason` `created_at` | - |
| `GET /api/v1/documents/{id}/isdoc` | `documents:read` | Soubor ke stažení | - | - |
| `GET /api/v1/documents/{id}/pdf` | `documents:read` | Soubor ke stažení | - | - |
| `GET /api/v1/orders` | `orders:read` | Stránka v data[], kurzor v next_cursor | `id` `order_number` `status` `payment_status` `payment_method` `fulfillment_status` `currency_code` `subtotal_minor` `discount_total_minor` `shipping_price_minor` `total_minor` `email` `phone` `is_quotation` `placed_at` `created_at` `updated_at` | `payment_status` `status` `updated_since` |
| `GET /api/v1/orders/{id}` | `orders:read` | Jeden objekt v data | `id` `order_number` `status` `payment_status` `payment_method` `fulfillment_status` `currency_code` `subtotal_minor` `discount_total_minor` `shipping_price_minor` `total_minor` `email` `phone` `is_quotation` `placed_at` `created_at` `updated_at` `billing{}` `shipping{}` `variable_symbol` `notes` `lines[]` | - |
| `GET /api/v1/ping` | libovolný platný klíč | Odpověď bez obálky | `ok` `key_id` `scopes[]` `api_version` | - |
| `GET /api/v1/pricing-rules` | `products:read` | Stránka v data[], kurzor v next_cursor | `id` `subscription_id` `scope` `scope_value` `markup_pct` `min_margin_minor` `rounding` `target` `priority` `is_active` `updated_at` | `updated_since` |
| `GET /api/v1/product-source-links` | `products:read` | Stránka v data[], kurzor v next_cursor | `id` `product_id` `variant_id` `subscription_id` `catalog_item_id` `locked_fields[]` `hidden_by_sync` `updated_at` | `updated_since` |
| `GET /api/v1/products` | `products:read` | Stránka v data[], kurzor v next_cursor | `id` `slug` `title` `status` `product_type` `brand` `price_minor` `currency_code` `image_url` `stock` `created_at` `updated_at` | `status` `updated_since` |
| `GET /api/v1/products/{id}` | `products:read` | Jeden objekt v data | `id` `slug` `title` `status` `product_type` `brand` `price_minor` `currency_code` `image_url` `stock` `created_at` `updated_at` `subtitle` `description` `meta_title` `meta_description` `published_at` `variants[]` `categories[]` `primary_category_id` | - |
| `GET /api/v1/returns` | `orders:read` | Stránka v data[], kurzor v next_cursor | `id` `return_number` `order_id` `order_number` `regime` `status` `reason` `refund{}` `credit_note_id` `credit_note_number` `deadline_at` `items[]` `created_at` `updated_at` | `status` `updated_since` |
| `GET /api/v1/returns/{id}` | `orders:read` | Jeden objekt v data | `id` `return_number` `order_id` `order_number` `regime` `status` `reason` `refund{}` `credit_note_id` `credit_note_number` `deadline_at` `items[]` `created_at` `updated_at` | - |
| `GET /api/v1/shipments` | `orders:read` | Stránka v data[], kurzor v next_cursor | `id` `order_id` `order_number` `carrier` `service` `delivery_type` `status` `tracking_number` `tracking_url` `pickup_point_id` `parcel_count` `weight_grams` `currency_code` `cod_amount_minor` `handed_over_at` `delivered_at` `items[]` `created_at` `updated_at` | `status` `updated_since` |
| `GET /api/v1/stock` | `stock:read` | Stránka v data[], kurzor v next_cursor | `variant_id` `product_id` `sku` `title` `variant` `on_hand` `available` `updated_at` `created_at` | `updated_since` |
| `GET /api/v1/stock/movements` | `stock:read` | Stránka v data[], kurzor v next_cursor | `id` `variant_id` `location_id` `type` `quantity_change` `quantity_before` `quantity_after` `reason` `reference{}` `created_at` | `type` `updated_since` |
| `GET /api/v1/supplier-approvals` | `products:read` | Stránka v data[], kurzor v next_cursor | `id` `subscription_id` `kind` `status` `product_id` `variant_id` `field` `current_value` `proposed_value` `reason` `decided_via` `decided_at` `created_at` `updated_at` | `kind` `status` `updated_since` |
| `GET /api/v1/supplier-orders` | `orders:read` | Stránka v data[], kurzor v next_cursor | `id` `order_id` `catalog_supplier_id` `status` `tracking_number` `carrier` `total_cost_minor` `currency_code` `sent_at` `shipped_at` `created_at` `updated_at` | `updated_since` |
| `GET /api/v1/supplier-subscriptions` | `products:read` | Stránka v data[], kurzor v next_cursor | `id` `feed_id` `catalog_supplier_id` `status` `trust_level` `auto_publish` `oos_behaviour` `ai_policy` `price_change_threshold_pct` `filter{}` `last_synced_at` `last_error` `created_at` `updated_at` | `updated_since` |
| `GET /api/v1/supplier-sync-runs` | `products:read` | Stránka v data[], kurzor v next_cursor | `id` `subscription_id` `trigger` `status` `started_at` `finished_at` `items_seen` `created` `updated` `hidden` `unhidden` `held` `skipped` `errors` `error` `created_at` `updated_at` | `subscription_id` `updated_since` |

Základ adresy je doména vašeho obchodu i s cestou `/api/v1`, tedy třeba
`https://muj-eshop.cz/api/v1/products`.

Tenhle článek je rozcestník a popisuje to, co platí napříč cestami. **Pole odpovědi, parametry
a chyby každé cesty** vypisuje reference generovaná ze smlouvy:

* [REST API: ověření klíče](/dokumentace/vyvojari/rest-klic)
* [REST API: objednávky, vratky a zásilky](/dokumentace/vyvojari/rest-objednavky)
* [REST API: produkty a kategorie](/dokumentace/vyvojari/rest-katalog)
* [REST API: zásoby a skladové pohyby](/dokumentace/vyvojari/rest-sklad)
* [REST API: doklady, PDF a ISDOC](/dokumentace/vyvojari/rest-doklady)
* [REST API: zákazníci](/dokumentace/vyvojari/rest-zakaznici)
* [REST API: feedy dodavatelů](/dokumentace/vyvojari/rest-feedy-dodavatelu)

## Obálka odpovědi [#obalka-odpovedi]

Výpisy vracejí `{ "data": [...], "next_cursor": "...", "next_updated_since": "..." }`, detaily
`{ "data": {...} }` a ověřovací cesta `/api/v1/ping` odpovídá bez obálky. PDF a ISDOC dokladu
přijdou jako soubor ke stažení. Jednotná obálka u výpisů znamená, že se stránkování i průběžná
synchronizace dají naprogramovat jednou pro všechny; jak, popisuje článek
[Stránkování a filtry](/dokumentace/vyvojari/strankovani-a-filtry).

> **Peníze jsou celá čísla v haléřích** Každé pole končící na `_minor` je částka v nejmenší jednotce měny, vedle ní stojí `currency_code`. Nikdy to není desetinné číslo a nikdy se nesmí převádět přes plovoucí čárku. Cena 1 299 Kč přijde jako 129900. Sazba DPH je celé procento: 21, 12 nebo 0.

## Čas je vždy ISO 8601 v UTC [#cas-je-vzdy-iso-8601-v-utc]

Každý čas v každé odpovědi má tvar `2026-09-01T08:14:02.118204Z`: ISO 8601 v UTC, s mikrosekundami
a se zónou `Z`. Datum bez času (datum vystavení, DUZP, splatnost) má tvar `RRRR-MM-DD`. Čas
z odpovědi jde do adresy dalšího volání vložit bez kódování, třeba šablonou v Make nebo v n8n.

> **Změna od 15. září 2026** Do té doby chodily časy objednávek, produktů, zásob a dokladů v textovém tvaru databáze, tedy `2026-09-01 08:14:02.118204+00` (mezera místo `T` a posun bez minut). Parser data a času si poradí s oběma tvary; kdo čas porovnával nebo ukládal jako text, má si ho teď převést na skutečný čas. Změnil se i tvar kurzoru, viz článek [Stránkování a filtry](/dokumentace/vyvojari/strankovani-a-filtry).

## Objednávky, vratky a zásilky [#objednavky-vratky-a-zasilky]

`/api/v1/orders` je cesta pro účetnictví, ERP a expedici: odpoví, kdo co objednal, v jakém je to
stavu a kolik to stálo. Nákupní ceny, marže, interní poznámky ani identifikátory platby z brány
přes API nechodí, takže report marží se staví nad daty v administraci, ne odsud.

`/api/v1/returns` vrací vratky (odstoupení od smlouvy) i reklamace. `credit_note_id` je identifikátor
vystaveného dobropisu a dá se rovnou poslat do `/api/v1/documents/{id}`.

`/api/v1/shipments` vrací zásilky. Štítek, stav komunikace s dopravcem ani jeho chybová hlášení ven
nejdou. Pole `items` bývá prázdné: verze 1 posílá celou objednávku jednou zásilkou a řádky zásilky
nezakládá, takže prázdné pole neznamená prázdnou zásilku.

> **Stav zásilky posouvá i čas objednávky** `fulfillment_status` v objednávce je stav nejnovější zásilky. Když zásilka vznikne nebo se posune, posune se i `updated_at` objednávky, takže změnu zachytí i průběžná synchronizace objednávek. Sledovací číslo, položky a další podrobnosti zásilky čtěte z `/api/v1/shipments`.

## Katalog [#katalog]

`/api/v1/products` vrací katalog, `/api/v1/products/{id}` jeden produkt i s variantami. Typický
odběratel je vlastní frontend, mobilní aplikace nebo srovnávač.

Cena je běžná koncová z maloobchodního ceníku. **Velkoobchodní a smluvní ceníky se přes API
neposílají**, protože jsou vázané na konkrétního zákazníka a klíč žádného zákazníka nezastupuje.

`/api/v1/categories` vrací strom kategorií jako plochý výpis; strom si složíte podle `parent_id`.

> **Změna ceny, obrázku ani zásoby čas produktu neposouvá** `updated_at` produktu se mění se záznamem produktu samotného, ne s cenou varianty, obrázkem ani zásobou. Zásoby synchronizujte přes `/api/v1/stock`; změnu ceny spolehlivě zachytí jen celý průchod katalogu bez `updated_since`, třeba jednou za noc.

## Sklad [#sklad]

`/api/v1/stock` vrací zásoby po variantách, sečtené přes všechny sklady, a to i u variant, u kterých
se zásoba nevede (ty mají nulu). Je to cesta pro externí sklad nebo WMS.

Řádek nese název produktu v `title` a název varianty v `variant`: hodnoty jejích voleb v pořadí,
v jakém je má produkt, oddělené lomítkem, třeba `Černá / 42`. Je to tentýž název, jaký obchodník
vidí na obrazovce Sklad, takže se podle něj domluvíte i u zboží bez SKU. U jednoduchého produktu,
který žádné volby nemá, je `variant` `null`. Tentýž název nese každá varianta v detailu produktu
(`/api/v1/products/{id}`).

> **`available` se dnes rovná `on_hand`** SHOPZI zásoby nerezervuje a objednávka se zásobou nehýbe vůbec: zásobu mění jen skladové pohyby (naskladnění, odpis, převod, inventura, příjem objednávky zboží nebo vrácení kusů z vratky). `available` proto dnes vrací totéž číslo co `on_hand`. Jsou to ale dva různé pojmy a až rezervace přijdou, rozejdou se, takže pro otázku „kolik můžu prodat“ čtěte `available`. Pole `reserved` odpověď nese do 11. září 2026 a vždycky v něm byla nula; odebrali jsme ho, aby z něj nikdo nepočítal rozdíly.

`/api/v1/stock/movements` vrací skladové pohyby. Pohyb je neměnný záznam, opravuje se novým
vyrovnávacím pohybem, ne přepsáním. Nákupní cena ani poznámka k pohybu ven nejdou. SKU, produkt ani
název varianty pohyb nenese: všechno se to může změnit, kdežto pohyb je neměnný a znovu by nepřišel.
Podle `variant_id` je najdete v `/api/v1/stock`.

Pole `reference` říká, k čemu pohyb patří, a jeho **výčet hodnot není uzavřený**. Běžně v něm
uvidíte objednávku zboží (`purchase_order`), vratku (`return`), inventuru (`stocktake`) nebo
smazání varianty (`variant_delete`), ale hodnota se vydává tak, jak ji zapsal ten, kdo pohyb
založil, takže může přibýt další a u pohybu bez vazby je `reference.type` i `reference.id` `null`.
Neznámá hodnota proto nesmí odběratele rozbít: větvete se na hodnotách, které znáte, a ostatní
nechte propadnout do obecné větve.

Obchod, který má **vypnutý modul Sklad**, neotevírá zásoby ani pohyby a obě cesty odpoví 403
s kódem `inventory_disabled`. Není to chyba oprávnění, je to vypnutá funkce obchodu. Stav zásoby
u produktu a u varianty ve výpisu katalogu ale chodí dál.

## Doklady [#doklady]

`/api/v1/documents` vrací vystavené doklady a filtrovat se dá podle `doc_type`.

`/api/v1/documents/{id}` vrací fakturu, proformu nebo dobropis celý, včetně **rekapitulace DPH po
sazbách** v `vat_breakdown`. Rekapitulace se čte doslova z vystaveného dokladu a nikdy se
nedopočítává z položek, takže sedí na haléř s tím, co dostal zákazník.

`/api/v1/documents/{id}/pdf` a `/api/v1/documents/{id}/isdoc` vydají doklad jako soubor: PDF, tedy
týž soubor, jaký stahuje administrace, a ISDOC 6.0.2 k importu do účetního programu. Odpověď nese
hlavičku `Content-Disposition` s názvem souboru podle čísla dokladu a `Cache-Control: private,
no-store`, takže ji cestou neuloží žádná sdílená mezipaměť. První stažení PDF, které se ještě
nevykreslovalo, trvá déle; další už jdou z úložiště. Každé stažení se počítá do stropu volání jako
kterékoli jiné volání.

PDF, které ještě nevzniklo, se vyrábí přímo při stažení, a proto jich server naráz vyrábí jen
několik. Když je plno, přijde 503 s kódem `pdf_busy` a hlavičkou `Retry-After`; počkejte a zkuste
to znovu. Při stahování dokladů za celé období proto stahujte postupně, ne všechny naráz.

> **Objednávka, nabídka a účtenka detail nemají** Výpis dokladů vrací všechny druhy, ale daňový detail, PDF a ISDOC mají jen faktura, proforma a dobropis. Na ostatní druhy odpoví všechny tři cesty 422 s kódem `unsupported_document_type`: doklad existuje, jen nemá tuhle podobu. Stornovaný dobropis zůstává ve výpisu, ale detail, PDF ani ISDOC už nevydá: odpoví 404 `not_found`. Když se PDF zrovna nepodaří vyrobit, přijde 502 `pdf_unavailable` a stačí to zkusit s odstupem znovu.

## Zákazníci [#zakaznici]

`/api/v1/customers` a `/api/v1/customers/{id}` vracejí zákazníky obchodu a chtějí oprávnění
`customers:read`. Odpověď obsahuje osobní údaje, a proto nese **jen vyjmenovaná pole**: druh
zákazníka (osoba, nebo firma), jméno, firmu, IČO a DIČ, e-mail a telefon, fakturační a doručovací
adresu, souhlas se zasíláním obchodních sdělení i s časem, kdy byl udělen, cenovou skupinu a časy
vzniku a poslední změny. Adresa je ta výchozí daného druhu, a když výchozí není, naposledy změněná.

Heslo ani jeho otisk, přihlašovací a ověřovací tokeny, počty neúspěšných přihlášení, vnitřní
poznámky obchodu ani nic z žádostí podle GDPR ven nejde, ani s tím nejširším klíčem. Tvar odpovědi
je pevný seznam, ne výběr ze sloupců databáze, takže nový sloupec u zákazníka se do API nedostane
sám.

`updated_at` zákazníka se posune i tehdy, když se změní nebo smaže jeho adresa nebo se změní jeho
cenová skupina, takže
průběžná synchronizace s `updated_since` zachytí i tyhle změny. `marketing_consent_at` je vyplněné,
jen když souhlas platí.

> **Oprávnění k zákazníkům dávejte jen tomu, kdo je potřebuje** Klíč s `customers:read` čte osobní údaje všech zákazníků obchodu. Zaškrtávejte ho jen u napojení, které je opravdu potřebuje (CRM, e-mailový nástroj), a pro katalog nebo sklad vytvořte klíč bez něj.

## Feedy dodavatelů [#feedy-dodavatelu]

Sedm cest popisuje modul Feedy dodavatelů: co obchod odebírá a s jakou mírou automatizace
(`/api/v1/supplier-subscriptions`), jak dopadly běhy synchronizace (`/api/v1/supplier-sync-runs`),
co čeká ve frontě ke schválení (`/api/v1/supplier-approvals`), které produkty pocházejí z feedu
a která pole si obchod převzal (`/api/v1/product-source-links`), podle jakých pravidel se počítá
prodejní cena (`/api/v1/pricing-rules`) a co nabízí katalog dodavatelů (`/api/v1/catalog-items`).
Všech šest chce oprávnění `products:read`. Sedmá cesta, `/api/v1/supplier-orders`, vrací dropship
části objednávek poslané dodavatelům a chce `orders:read`.

Všechny jsou **jen ke čtení**. Odběr, cenová pravidla, rozhodnutí ve frontě, žádost o přístup
k dodavateli i vlastní feed se zapisují přes server MCP nástrojem `shopzi_manage_supply`, viz
[MCP: sklad a nákup zboží](/dokumentace/vyvojari/mcp-sklad), nebo v administraci.

Nákupní cenu v `/api/v1/catalog-items` uvidíte jen tam, kde ji obchod smí znát: u vlastního feedu,
u dodavatele bez schvalování přístupu a po schválené žádosti o přístup; jinak je `cost_minor`
`null`. Ceny položek katalogu jsou v setinách měny feedu, měnu říká `currency_code`. Obchod, jehož
tarif feedy dodavatelů nezahrnuje, dostane na téhle cestě `402 plan_required`.

Feedy dodavatelů nepatří pod modul Sklad, takže tyhle cesty odpovídají i obchodu s vypnutým skladem.
Změny ze synchronizace navíc ohlašují webhooky (`supplier_sync.*`, `supplier_order.*`,
`stock.changed` a další), viz [Webhooky: příjem událostí](/dokumentace/vyvojari/webhooky).

## Ověření klíče [#overeni-klice]

`/api/v1/ping` nevrací data obchodu, jen identitu klíče a jeho oprávnění. Volá se jako první, ještě
než postavíte scénář. Podrobněji v článku
[Autentizace a oprávnění](/dokumentace/vyvojari/autentizace-a-opravneni).

## Strop volání a záznam o volání [#strop-volani-a-zaznam-o-volani]

Každá cesta počítá volání **na klíč** podle tarifu obchodu, v pevném minutovém a denním okně.
Odpověď na volání, které prošlo všemi branami, tedy klíčem, plochou, tarifem i oprávněním, nese
hlavičky `X-RateLimit-Limit`, `X-RateLimit-Remaining` a `X-RateLimit-Reset`; nad strop přijde
`429 rate_limited` s hlavičkou `Retry-After` a týmiž hlavičkami. Čísla pro jednotlivé tarify
a doporučený postup jsou v článku [Chybové odpovědi](/dokumentace/vyvojari/chybove-odpovedi).

Obchod si o voláních ukládá záznam **bez obsahu**: čas, šablonu cesty (`/api/v1/orders/{id}`,
ne skutečný identifikátor ani parametry dotazu), výsledek a stavový kód, dobu zpracování, klíč,
IP adresu a název klienta z hlavičky `User-Agent`. Tělo požadavku ani odpovědi se neukládá nikde.
Neúspěšných volání (chyby, odmítnutí, 429) se takhle zapíše jen omezený počet: bez platného klíče
nejvýš 60 za hodinu na obchod, s platným klíčem nejvýš 1 000 za kalendářní den na klíč a plochu.
Nad tyto počty zůstávají jen denní součty na klíč (kolik volání, chyb, odmítnutí a volání nad
strop), žádný záznam o jednotlivém volání. Úspěšná volání se zapisují vždy. Záznamy mažeme po
30 dnech, denní součty na klíč po 90 dnech.

Záznam vidí vlastník nebo manažer obchodu v **Nastavení → API a MCP**:

* část **Protokol volání** ukazuje jednotlivá volání s filtry **Plocha**, **Výsledek** a **Klíč**
  a u odmítnutí i kód chyby, tentýž, jaký dostal klient;
* část **Využití** ukazuje za zvolené období součty volání, chyb, odmítnutí a volání nad limit, i po
  jednotlivých klíčích;
* v kartě **API klíče** ukazuje u každého klíče sloupec **Naposledy** čas posledního použití a pod
  ním počet dnešních volání.

## Verzování [#verzovani]

Verze je v cestě. Cokoli, co by rozbilo existujícího odběratele (přejmenované pole, zmizelé pole,
změněný typ), znamená novou verzi cesty, ne tichou změnu téhle. Nové pole v odpovědi ani nová
hodnota výčtu za takovou změnu nepovažujeme, takže parser musí neznámá pole a hodnoty ignorovat, ne
na nich padat. Celý slib je v článku [OpenAPI popis REST API](/dokumentace/vyvojari/openapi).
