# REST API: produkty a kategorie

> Referenční popis cest katalogu, tedy výpisu a detailu produktu a stromu kategorií: parametry, pole odpovědi, chyby a ukázka volání.

- Sekce: Pro vývojáře
- Stránka: https://shopzi.cz/dokumentace/vyvojari/rest-katalog
- Ověřeno: 2026-09-16

---

Pro vlastní frontend, srovnávače a mobilní aplikaci: katalog s variantami, cenami, dostupností a stromem kategorií.

Cesty odpovídají na `GET` na doméně vašeho obchodu, například `https://muj-eshop.cz/api/v1/…`, a klíč se posílá v hlavičce `Authorization: Bearer` nebo `x-api-key`. Parametry, pole i chyby na téhle stránce se generují ze smlouvy, podle které API odpovídá, takže platí pro build, na kterém stránka běží. Každá cesta může navíc vrátit chyby společné všem cestám: `invalid_api_key`, `missing_api_key`, `external_access_disabled`, `external_access_not_enabled`, `external_access_suspended`, `key_surface_mismatch`, `plan_required`, `shop_unavailable`, `shop_not_found`, `rate_limited` a `server_error`. Co znamenají, popisuje článek [Chybové odpovědi](/dokumentace/vyvojari/chybove-odpovedi), klíč a oprávnění [Autentizace a oprávnění](/dokumentace/vyvojari/autentizace-a-opravneni) a průchod výpisem [Stránkování a filtry](/dokumentace/vyvojari/strankovani-a-filtry).

## `GET /api/v1/products` [#get-api-v1-products]

Katalog, bez updated\_since od nejnovějšího produktu. Cena je běžná koncová z ceníku pro maloobchod, velkoobchodní ceny se přes API neposílají.

* **Oprávnění:** `products:read`, u klíče „Katalog: produkty, kategorie, dodavatelé (čtení)“.
* **Odpověď:** stránka výpisu, záznamy jsou v `data` a jak otevřít další stránku, říká seznam Obálka odpovědi.
* **Synchronizace:** s `updated_since` se výpis řadí podle pole `updated_at` od nejstarší změny, bez něj od nejnovějšího záznamu. Pole `updated_at`: Poslední změna záznamu produktu. Cena, obrázek a zásoba ho neposouvají.

```bash
curl -s "https://muj-eshop.cz/api/v1/products?limit=100" \
  -H "Authorization: Bearer $SHOPZI_API_KEY"
```

**Parametry dotazu**

* `limit` (celé číslo od 1 do 100, výchozí `25`; nepovinný): Počet řádků na stránku, výchozí 25. Vyšší hodnota se ořízne na 100, nesmysl spadne na výchozí.
* `cursor` (text; nepovinný): Hodnota `next_cursor` z předchozí stránky, beze změny. Platí jen pro tentýž dotaz.
* `updated_since` (čas ISO 8601; nepovinný): ISO 8601 se zónou `Z` nebo s číselným posunem (`+02:00`). Vrátí záznamy změněné od té chvíle včetně, seřazené od nejstarší změny, a stránkuje stabilně: řádek změněný během průchodu se v něm neopakuje a přijde v příštím. Hodnota `next_updated_since` jde do adresy vložit bez kódování; `+` z nezakódované adresy se čte jako `+`.
* `status` (jedna z hodnot `draft`, `active` a `archived`; nepovinný): Stav publikace produktu.

**Obálka odpovědi**

* `data` (pole záznamů): Záznamy stránky, jejich pole popisuje seznam Pole záznamu.
* `next_cursor` (text nebo `null`): Kurzor další stránky, `null` = tohle je poslední stránka.
* `next_updated_since` (čas ISO 8601): Hodnota `updated_since` pro příští průchod: začátek tohoto průchodu mínus 5 minut. Na všech stránkách jednoho průchodu je stejná a do adresy jde vložit bez kódování.

**Pole záznamu**

* `id` (UUID)
* `slug` (text)
* `title` (text)
* `status` (jedna z hodnot `draft`, `active` a `archived`)
* `product_type` (jedna z hodnot `physical`, `digital`, `service`, `gift_card`, `bundle` a `subscription`)
* `brand` (text nebo `null`)
* `price_minor` (celé číslo nebo `null`): Běžná koncová cena z maloobchodního ceníku v okamžiku čtení, v haléřích s DPH.
* `currency_code` (text se vzorem `^[A-Z]{3}$`): Kód měny podle ISO 4217, dnes vždy CZK.
* `image_url` (text nebo `null`)
* `stock` (číslo nebo `null`): Kusy na skladě v okamžiku čtení, `null` = zásoba se nevede. Zásoby se synchronizují přes `/stock`.
* `created_at` (čas ISO 8601)
* `updated_at` (čas ISO 8601): Poslední změna záznamu produktu. Cena, obrázek a zásoba ho neposouvají.

**Chyby této cesty**

* **400** `invalid_query`: Parametr dotazu má neplatnou hodnotu: čas v updated\_since bez časového posunu, nebo hodnota filtru, kterou výčet nezná. Zpráva jmenuje parametr, u filtru i povolené hodnoty.
* **400** `invalid_cursor`: Kurzor je poškozený, nebo patří k jinému dotazu (jiná cesta, jiný filtr nebo jiné updated\_since). Začněte průchod znovu bez cursor.
* **403** `insufficient_scope`: Klíč je platný, ale nemá oprávnění, které cesta vyžaduje. Zpráva vypíše, co klíči chybí a co má.

## `GET /api/v1/products/{id}` [#get-api-v1-products-id]

Jeden produkt i s variantami, jejich názvy, cenami a dostupností a se zařazením do kategorií.

* **Oprávnění:** `products:read`, u klíče „Katalog: produkty, kategorie, dodavatelé (čtení)“.
* **Odpověď:** jeden záznam v `data`.

```bash
curl -s "https://muj-eshop.cz/api/v1/products/$ID" \
  -H "Authorization: Bearer $SHOPZI_API_KEY"
```

**Parametry v cestě**

* `id` (UUID; povinný): Identifikátor záznamu (UUID) z výpisu.

**Obálka odpovědi**

* `data` (záznam): Jeho pole popisuje seznam Pole záznamu.

**Pole záznamu**

* `id` (UUID)
* `slug` (text)
* `title` (text)
* `status` (jedna z hodnot `draft`, `active` a `archived`)
* `product_type` (jedna z hodnot `physical`, `digital`, `service`, `gift_card`, `bundle` a `subscription`)
* `brand` (text nebo `null`)
* `price_minor` (celé číslo nebo `null`): Běžná koncová cena z maloobchodního ceníku v okamžiku čtení, v haléřích s DPH.
* `currency_code` (text se vzorem `^[A-Z]{3}$`): Kód měny podle ISO 4217, dnes vždy CZK.
* `image_url` (text nebo `null`)
* `stock` (číslo nebo `null`): Kusy na skladě v okamžiku čtení, `null` = zásoba se nevede. Zásoby se synchronizují přes `/stock`.
* `created_at` (čas ISO 8601)
* `updated_at` (čas ISO 8601): Poslední změna záznamu produktu. Cena, obrázek a zásoba ho neposouvají.
* `subtitle` (text nebo `null`)
* `description` (text nebo `null`)
* `meta_title` (text nebo `null`)
* `meta_description` (text nebo `null`)
* `published_at` (čas ISO 8601 nebo `null`)
* `variants` (pole objektů)
* `variants[].id` (UUID)
* `variants[].variant` (text nebo `null`): Název varianty z hodnot jejích voleb v pořadí voleb produktu, oddělených lomítkem, například `Černá / 42`. `null`, když varianta žádné volby nemá (jednoduchý produkt).
* `variants[].sku` (text nebo `null`)
* `variants[].ean` (text nebo `null`)
* `variants[].is_default` (`true` nebo `false`)
* `variants[].price_minor` (celé číslo nebo `null`)
* `variants[].compare_at_minor` (celé číslo nebo `null`)
* `variants[].currency_code` (text se vzorem `^[A-Z]{3}$`): Kód měny podle ISO 4217, dnes vždy CZK.
* `variants[].available` (číslo nebo `null`)
* `categories` (pole objektů)
* `categories[].id` (UUID)
* `categories[].name` (text)
* `categories[].full_slug` (text)
* `categories[].is_primary` (`true` nebo `false`)
* `primary_category_id` (UUID nebo `null`)

**Chyby této cesty**

* **403** `insufficient_scope`: Klíč je platný, ale nemá oprávnění, které cesta vyžaduje. Zpráva vypíše, co klíči chybí a co má.
* **404** `not_found`: Záznam s tímhle identifikátorem v obchodě není.

## `GET /api/v1/categories` [#get-api-v1-categories]

Strom kategorií jako plochý výpis: nadřazená kategorie, hloubka, pořadí, celá cesta ze slugů a to, jestli je kategorie na obchodě vidět.

* **Oprávnění:** `products:read`, u klíče „Katalog: produkty, kategorie, dodavatelé (čtení)“.
* **Odpověď:** stránka výpisu, záznamy jsou v `data` a jak otevřít další stránku, říká seznam Obálka odpovědi.
* **Synchronizace:** s `updated_since` se výpis řadí podle pole `updated_at` od nejstarší změny, bez něj od nejnovějšího záznamu.

```bash
curl -s "https://muj-eshop.cz/api/v1/categories?limit=100" \
  -H "Authorization: Bearer $SHOPZI_API_KEY"
```

**Parametry dotazu**

* `limit` (celé číslo od 1 do 100, výchozí `25`; nepovinný): Počet řádků na stránku, výchozí 25. Vyšší hodnota se ořízne na 100, nesmysl spadne na výchozí.
* `cursor` (text; nepovinný): Hodnota `next_cursor` z předchozí stránky, beze změny. Platí jen pro tentýž dotaz.
* `updated_since` (čas ISO 8601; nepovinný): ISO 8601 se zónou `Z` nebo s číselným posunem (`+02:00`). Vrátí záznamy změněné od té chvíle včetně, seřazené od nejstarší změny, a stránkuje stabilně: řádek změněný během průchodu se v něm neopakuje a přijde v příštím. Hodnota `next_updated_since` jde do adresy vložit bez kódování; `+` z nezakódované adresy se čte jako `+`.

**Obálka odpovědi**

* `data` (pole záznamů): Záznamy stránky, jejich pole popisuje seznam Pole záznamu.
* `next_cursor` (text nebo `null`): Kurzor další stránky, `null` = tohle je poslední stránka.
* `next_updated_since` (čas ISO 8601): Hodnota `updated_since` pro příští průchod: začátek tohoto průchodu mínus 5 minut. Na všech stránkách jednoho průchodu je stejná a do adresy jde vložit bez kódování.

**Pole záznamu**

* `id` (UUID)
* `parent_id` (UUID nebo `null`): Nadřazená kategorie, `null` u kořene.
* `name` (text)
* `slug` (text)
* `full_path` (text): Celá cesta ze slugů od kořene, například `obyvak/sedacky`.
* `depth` (celé číslo): Hloubka ve stromu, kořen má 0.
* `position` (celé číslo): Pořadí mezi sourozenci.
* `is_active` (`true` nebo `false`): Kategorie je na obchodě vidět.
* `show_in_menu` (`true` nebo `false`): Kategorie je v hlavní nabídce.
* `created_at` (čas ISO 8601)
* `updated_at` (čas ISO 8601)

**Chyby této cesty**

* **400** `invalid_query`: Parametr dotazu má neplatnou hodnotu: čas v updated\_since bez časového posunu, nebo hodnota filtru, kterou výčet nezná. Zpráva jmenuje parametr, u filtru i povolené hodnoty.
* **400** `invalid_cursor`: Kurzor je poškozený, nebo patří k jinému dotazu (jiná cesta, jiný filtr nebo jiné updated\_since). Začněte průchod znovu bez cursor.
* **403** `insufficient_scope`: Klíč je platný, ale nemá oprávnění, které cesta vyžaduje. Zpráva vypíše, co klíči chybí a co má.
