Založte si e-shop zdarma a získejte 10 000 AI kreditů na start. Bez karty, bez závazku.E-shop zdarma + 10 000 AI kreditů na start

Začít zdarma
Přeskočit na obsah

Dokumentace pro vývojáře

Jak k obchodu na SHOPZI připojit vlastní program nebo asistenta: REST API jen ke čtení, MCP server, webhooky a založení obchodu z terminálu.

Ověřeno 20. 10. 2026Markdown verze

Tahle dokumentace je pro vás, pokud k obchodu na SHOPZI připojujete vlastní program nebo asistenta. Popisuje rozhraní: adresy, hlavičky, oprávnění, tvary odpovědí a chyby. Jak vlastník obchodu přístup zapne a vydá klíč, popisuje uživatelská dokumentace v článku API a MCP.

Co obchod nabízí

  • REST API jen ke čtení, na doméně obchodu pod cestou /api/v1. Hodí se pro synchronizaci s účetnictvím, skladem nebo ERP. Cesty popisuje článek Endpointy REST API.
  • MCP server na doméně obchodu pod cestou /api/mcp, přes který s obchodem pracuje asistent. Nastavení klienta popisuje článek Připojení asistenta k obchodu.
  • Webhooky, kterými obchod sám ohlásí změnu vašemu koncovému bodu. Co má umět příjemce, popisuje článek Webhooky: příjem událostí.
  • Založení nového obchodu z terminálu přes server MCP bez klíče na https://shopzi.cz/api/mcp, viz článek Založení obchodu přes MCP.

Kde začít

  1. V článku Rozhraní pro vývojáře zjistíte, čím se REST API a MCP server liší a co si připravit.
  2. Vlastník obchodu otevře Nastavení → API a MCP, v části Přístup a klíče zapne v kartě Externí přístup plochu, kterou napojení potřebuje (REST API, Server MCP, nebo obě), a v kartě API klíče vydá klíč tlačítkem Vytvořit klíč.
  3. Podle článku Autentizace a oprávnění se klíčem prokážete a pošlete první požadavek.
  4. Když požadavek selže, kód z odpovědi vysvětluje článek Chybové odpovědi.

Čtyři rychlé starty níž vedou od prázdného terminálu k prvnímu funkčnímu napojení. Příklady počítají s obchodem na doméně muj-eshop.cz se subdoménou muj-eshop; dosaďte svoje.

Rychlý start: asistent přes MCP

Předpoklad: vlastník obchodu zapnul Server MCP a vydal klíč, který platí pro server MCP (v poli Kde klíč platí volba Jen server MCP, nebo REST API i server MCP). Hotové nastavení klienta ukazuje administrace v Nastavení → API a MCP dvakrát: v dialogu Klíč je vytvořený hned po vytvoření klíče, kde je klíč už doplněný, a trvale v kartě Připojení klienta, kde místo klíče stojí VÁŠ_API_KLÍČ. V obou se přepíná mezi Claude Code, Cursor, VS Code a Jiný klient. Adresa serveru je doména, na které máte administraci otevřenou, s cestou /api/mcp. Jméno serveru v klientovi se skládá z shopzi- a subdomény obchodu, aby šlo mít připojených víc obchodů naráz.

Claude Code: příkaz spusťte v terminálu, server se přidá pro aktuální projekt.

claude mcp add --transport http shopzi-muj-eshop https://muj-eshop.cz/api/mcp --header "Authorization: Bearer VÁŠ_API_KLÍČ"

Cursor a jiní klienti: Cursor čte ~/.cursor/mcp.json (všechny projekty) nebo .cursor/mcp.json v projektu a stejný tvar mcpServers čte většina ostatních klientů.

{
  "mcpServers": {
    "shopzi-muj-eshop": {
      "url": "https://muj-eshop.cz/api/mcp",
      "headers": {
        "Authorization": "Bearer VÁŠ_API_KLÍČ"
      }
    }
  }
}

VS Code: nastavení patří do .vscode/mcp.json a klíč se do souboru nepíše, VS Code se na něj zeptá při spuštění serveru. Soubor tak jde bez obav uložit do repozitáře.

{
  "inputs": [
    {
      "type": "promptString",
      "id": "shopzi-api-key",
      "description": "API klíč obchodu SHOPZI",
      "password": true
    }
  ],
  "servers": {
    "shopzi-muj-eshop": {
      "type": "http",
      "url": "https://muj-eshop.cz/api/mcp",
      "headers": {
        "Authorization": "Bearer ${input:shopzi-api-key}"
      }
    }
  }
}

Než se začnete ptát v chatu, ověřte spojení voláním tools/list. Zahajovací výměna se nečeká a odpověď obsahuje jen nástroje, na které má klíč oprávnění. Jejich parametry popisuje reference, například MCP: katalog produktů nebo MCP: objednávky, vratky a doklady.

curl -s https://muj-eshop.cz/api/mcp \
  -H "Authorization: Bearer $SHOPZI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

Obchod, který ještě neexistuje, nemá doménu ani klíč. K jeho založení se připojte k serveru bez klíče na shopzi.cz; hlavičku Authorization neposílejte.

claude mcp add --transport http shopzi-zalozeni https://shopzi.cz/api/mcp

Nástroje tohoto serveru popisuje MCP bez klíče: tarify a založení obchodu a celý průběh až po předání klíče článek Založení obchodu přes MCP. Transport, potvrzování změn a stropy popisuje článek MCP server, časté potíže s klientem Připojení asistenta k obchodu.

Rychlý start: první volání REST API

Předpoklad: vlastník obchodu zapnul v Nastavení → API a MCP REST API a vydal klíč, který platí pro REST API (v poli Kde klíč platí volba Jen REST API, nebo REST API i server MCP) a má oprávnění, které cesta chce. Výpis objednávek chce orders:read. Adresa je doména obchodu s cestou /api/v1 a všechny cesty odpovídají jen na GET.

Nejdřív klíč ověřte. Ping nevrací data obchodu, jen identitu klíče a jeho oprávnění, a stačí mu libovolný platný klíč pro REST API. Totéž volání s vaším klíčem ukazuje i dialog Klíč je vytvořený v části Ověření přes REST API:

export SHOPZI_API_KEY="shopzi_sk_..."

curl -s https://muj-eshop.cz/api/v1/ping \
  -H "Authorization: Bearer $SHOPZI_API_KEY"

Pak stáhněte první stránku objednávek:

curl -s "https://muj-eshop.cz/api/v1/orders?limit=50" \
  -H "Authorization: Bearer $SHOPZI_API_KEY"

Výpis vrací obálku { "data": [...], "next_cursor": "...", "next_updated_since": "..." }. Dokud next_cursor není null, pošlete jeho hodnotu beze změny v parametru cursor se stejnými filtry. Kurzor je zapsaný v base64url, takže do adresy jde vložit bez kódování. Tady s pomocí jq:

CURSOR=$(curl -s "https://muj-eshop.cz/api/v1/orders?limit=50" \
  -H "Authorization: Bearer $SHOPZI_API_KEY" | jq -r '.next_cursor')

curl -s "https://muj-eshop.cz/api/v1/orders?limit=50&cursor=$CURSOR" \
  -H "Authorization: Bearer $SHOPZI_API_KEY"

Když odpověď místo dat nese error, rozhodujte se podle error.code, ne podle textu: insufficient_scope znamená, že klíči chybí oprávnění, external_access_not_enabled, že REST API není zapnuté. Všechny kódy vysvětluje článek Chybové odpovědi. Pole, filtry a chyby každé cesty popisuje reference, například REST API: objednávky, vratky a zásilky a REST API: ověření klíče.

Rychlý start: průběžná synchronizace

Synchronizace stojí na parametru updated_since REST API. Výpis s ním vrací jen záznamy změněné od zadaného času včetně, od nejstarší změny, a průchod je stabilní: záznam, který se změní uprostřed průchodu, se v něm neopakuje a přijde v příštím.

  • První běh pošle updated_since hluboko v minulosti, třeba 2000-01-01T00:00:00Z, ať přijde všechno.
  • Stránky procházejte kurzorem. next_cursor posílejte zpátky se stejným updated_since i filtry; kurzor k jinému dotazu API odmítne kódem 400 invalid_cursor.
  • Konec průchodu: po poslední stránce si uložte next_updated_since jako updated_since příštího běhu. Je to začátek průchodu mínus 5 minut, takže příští běh vrátí poslední změny znovu. Záznamy proto ukládejte přepsáním podle id, nikdy přidáváním.
  • Navázání po pádu: kurzor si ukládejte po každé zpracované stránce. Když běh spadne, pošlete uložený kurzor znovu se stejným updated_since a průchod doběhne k téže hranici. Když kurzor vrátí 400 invalid_cursor, začněte průchod znovu od uloženého updated_since; přepsáním podle id se nic nezdvojí.
  • Odpověď 429 znamená, že klíč vyčerpal strop volání za minutu nebo za den. Počkejte tolik sekund, kolik říká hlavička Retry-After, a pošlete tentýž požadavek se stejným kurzorem. Po vyčerpání denního stropu míří Retry-After až na půlnoc českého času. Tempo si můžete řídit i dopředu podle hlaviček X-RateLimit-Remaining a X-RateLimit-Reset.
  • Odpověď 500 je chyba na naší straně: běh ukončete a zkuste ho znovu s prodlužujícím se odstupem. Uložený kurzor ho nechá navázat.
// Stav mezi běhy: { since, cursor }. Ukládá se po každé stránce.
export async function syncOrders(state, { upsertById, saveState }) {
  for (;;) {
    const params = new URLSearchParams({ updated_since: state.since, limit: "100" });
    if (state.cursor) params.set("cursor", state.cursor);

    const res = await fetch(`https://muj-eshop.cz/api/v1/orders?${params}`, {
      headers: { Authorization: `Bearer ${process.env.SHOPZI_API_KEY}` },
    });

    if (res.status === 429) {
      const wait = Number(res.headers.get("retry-after") ?? "60");
      await new Promise((resolve) => setTimeout(resolve, wait * 1000));
      continue; // tentýž požadavek se stejným kurzorem
    }
    if (res.status === 400) {
      const { error } = await res.json();
      if (error.code !== "invalid_cursor" || !state.cursor) throw new Error(error.message);
      state.cursor = null; // průchod znovu od uloženého updated_since
      continue;
    }
    if (!res.ok) throw new Error(`SHOPZI ${res.status}`);

    const page = await res.json();
    await upsertById(page.data); // přepsat podle id, nikdy přidávat
    if (page.next_cursor) {
      state.cursor = page.next_cursor;
    } else {
      state.since = page.next_updated_since; // hranice příštího běhu
      state.cursor = null;
    }
    await saveState(state);
    if (!state.cursor) return state;
  }
}

Stejně se synchronizují produkty, kategorie, zákazníci, vratky, zásilky, zásoby, doklady, skladové pohyby i data feedů dodavatelů (odběry, katalog dodavatelů, fronta ke schválení a dropship objednávky, viz REST API: feedy dodavatelů). Některé cesty stránkují podle času vzniku místo času změny a změnu ceny varianty čas produktu neposouvá; obojí i s dalšími podrobnostmi popisuje článek Stránkování a filtry, stropy volání podle tarifu článek Chybové odpovědi.

Rychlý start: příjem webhooků

Koncový bod zaregistruje vlastník nebo manažer obchodu v Nastavení → API a MCP v části Webhooky tlačítkem Přidat koncový bod: vyplní Adresu koncového bodu s https a zaškrtne události, které má dostávat. Hned po uložení se ukáže Podpisové tajemství s prefixem whsec_, kterým si podpis ověříte; znovu ho zobrazí tlačítko Zobrazit tajemství. Každá zpráva je POST s tělem { "id", "type", "created_at", "data" } a hlavičkami webhook-id, webhook-timestamp a webhook-signature podle specifikace Standard Webhooks.

Příjemce v Node.js bez závislostí: ověří razítko a podpis nad surovým tělem, odpoví hned a tutéž zprávu zpracuje jen jednou.

import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";

const SECRET = process.env.SHOPZI_WEBHOOK_SECRET; // whsec_...
const TOLERANCE_SEC = 300;
const processed = new Set(); // v provozu tabulka nebo fronta, ne paměť procesu

function verify(raw, headers) {
  const id = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  const signature = headers["webhook-signature"];
  if (!id || !timestamp || !signature) return false;

  const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
  if (!Number.isFinite(age) || age > TOLERANCE_SEC) return false;

  const key = Buffer.from(SECRET.replace(/^whsec_/, ""), "base64");
  const expected = Buffer.from(
    createHmac("sha256", key).update(`${id}.${timestamp}.${raw}`).digest("base64"),
  );
  return signature.split(" ").some((part) => {
    if (!part.startsWith("v1,")) return false;
    const presented = Buffer.from(part.slice(3));
    return presented.length === expected.length && timingSafeEqual(presented, expected);
  });
}

createServer((req, res) => {
  const chunks = [];
  req.on("data", (chunk) => chunks.push(chunk));
  req.on("end", () => {
    const raw = Buffer.concat(chunks).toString("utf8");
    if (req.method !== "POST" || !verify(raw, req.headers)) {
      res.writeHead(401).end();
      return;
    }
    res.writeHead(204).end(); // odpovědět hned, zpracovat potom

    const id = req.headers["webhook-id"];
    if (processed.has(id)) return; // táž zpráva podruhé
    processed.add(id);
    const event = JSON.parse(raw);
    console.log(event.type, event.data);
  });
}).listen(3000);

Co z toho plyne pro provoz:

  • Odpovězte do 10 sekund kódem 2xx. Pomalejší odpověď, jiný kód i přesměrování se počítá jako nezdar; přesměrování nenásledujeme.
  • Po nezdaru posíláme znovu po 1 minutě, 5 minutách, 30 minutách, 2 hodinách a 6 hodinách, celkem 6 pokusů. Odpověď 410 říká, že koncový bod už neexistuje, a další pokus po ní nepřijde. Koncový bod, kterému za sebou 10 doručení skončí nezdarem, se sám vypne.
  • Táž zpráva může dorazit vícekrát se stejným webhook-id. Deduplikujte podle něj, ne podle obsahu těla.
  • Webhooky se řídí REST API. Když je REST API obchodu zavřené (vypnutý přepínač, obchod bez nároku na externí přístup, tedy tarif Free nebo zkušební období bez uložené karty, pozastavení od SHOPZI, nebo REST API dočasně vypnuté pro všechny obchody), nové události se neposílají a po znovuotevření se nedoženou. Zprávy čekající na další pokus skončí v Logu doručení jako Vzdáno a po znovuotevření je jde poslat odkazem Odeslat znovu.

Seznam událostí, obsah pole data, zbytek smlouvy příjemce a postup pro Make.com a n8n popisuje článek Webhooky: příjem událostí.