> ## Documentation Index
> Fetch the complete documentation index at: https://andcze.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK TypeScript

> @itemshop/sdk — storefront bez klucza, hooki React, klient API z kluczem i CLI.

`@itemshop/sdk` to referencyjne SDK ItemShop: zero zależności runtime, ESM + CJS, działa w przeglądarce, Node.js 20+, Bun, Deno i Cloudflare Workers (poza CLI używa tylko `fetch`). W pakiecie jest też [CLI](https://andcze.mintlify.site/pl/site/cli.md).

| Import | Gdzie działa | Co to jest |
| - | - | - |
| `@itemshop/sdk/storefront` | przeglądarka, dowolne JS | `createStorefront()` — dane sklepu i kasa. **Bez sekretów.** |
| `@itemshop/sdk/client` | tylko serwer | `createClient({ apiKey })` — całe API z kluczem sklepu |
| `@itemshop/sdk/react` | React 18+ | `StorefrontProvider`, `useShop`, `useProducts`, `useCart`, `useCheckout`… |
| `@itemshop/sdk/vite` | konfiguracja Vite | wtyczka wstrzykująca `window.__ITEMSHOP__` w `vite dev` |
| `itemshop` (bin) | terminal | `login`, `init`, `dev`, `deploy`, `pull`, `skills`, `mcp` |

`@itemshop/sdk` re-eksportuje oba wejścia: storefront i klienta.

## Instalacja

```bash theme={null}
npm install @itemshop/sdk
```

## Storefront (bez klucza)

Na stronie hostowanej przez ItemShop `window.__ITEMSHOP__ = { shopId, apiBase, locale }` jest wstrzykiwane automatycznie, więc konfiguracja nie jest potrzebna:

```ts theme={null}
import { createStorefront, ItemShopError } from "@itemshop/sdk/storefront";

const sf = createStorefront(); // albo createStorefront({ shopId, apiBase, locale }) / ({ domain: "sklep.example.pl" })

const shop = await sf.shop.get();                          // displayName, logo, waluta, języki, serwery (isOnline), regulamin…
const { products, categories } = await sf.products.list({ category: categoryId }); // kategoria = serwer
const vip = await sf.products.get("ranga-vip");            // slug albo id
const { name } = sf.localize(vip);                         // tłumaczenie z fallbackiem
const methods = await sf.paymentMethods.list();            // jedna pozycja na kanał: { code, channel, name, icon, testMode, limits }

const quote = await sf.discounts.validate("LATO10", [{ productId: vip.id }]);

try {
  const { redirectUrl, orderNumber, statusKey } = await sf.checkout.create({
    items: [{ productId: vip.id, quantity: 1 }],           // 1–20 pozycji; 2+ = jedna grupa płatności
    nick: "Steve",                                         // sklepy gier; sklepy ogólne używają `email`
    email: "steve@example.com",
    discountCode: quote.valid ? "LATO10" : undefined,
    consents: { terms: true, immediateDelivery: true, legalVersion: shop.legal.version },
    paymentMethodCode: methods[0]!.code,
    paymentChannel: methods[0]!.channel ?? undefined,
  });
  sf.checkout.redirect({ redirectUrl });                   // bramka albo strona statusu (zamówienie darmowe)
} catch (error) {
  if (ItemShopError.is(error) && error.code === "LEGAL_DOCUMENTS_CHANGED") reloadTerms();
  else throw error;
}

// powrót z bramki: platforma dopisuje ?itemshop_order=<number>&itemshop_key=<key> do Twojego returnUrl
const order = sf.checkout.returnedOrder();                 // { number, key } albo null
if (order) {
  const status = await sf.orders.statusByNumber(order.number, order.key); // { status, items[], delivery, … }
  if (status.delivery?.ready) {
    const { items } = await sf.orders.delivery(order.number, order.key);  // klucze, tekst, link do pliku
  }
}

// realizacja vouchera — tworzy darmowe zamówienie, bez ponowień (kod jednorazowy)
await sf.vouchers.redeem("ABC-123", "Steve");

// widżety — puste, gdy właściciel wyłączył dane publiczne
await sf.widgets.recentPurchases({ limit: 5 });
await sf.widgets.topSpenders({ limit: 10 });
await sf.widgets.monthlyGoal();                            // { enabled, percent }
await sf.widgets.onlinePlayers();                          // { count, players: string[] }
```

Strona nigdy nie dotyka danych płatniczych — `checkout.create` zwraca `redirectUrl` do bramki. Gdy pominiesz `consents.legalVersion`, SDK wyśle bieżącą `shop.legal.version`; podaj wersję, którą pokazałeś kupującemu, żeby nieaktualna strona dostała `LEGAL_DOCUMENTS_CHANGED`. Po `CAPTCHA_REQUIRED` powtórz kasę z `captchaToken`. Typy (`StorefrontShop`, `StorefrontProduct`, `CheckoutResult`…) są generowane ze specyfikacji [Storefront API](https://andcze.mintlify.site/pl/developers/storefront-api.md).

## React

```tsx theme={null}
import { StorefrontProvider, useShop, useProducts, useCart, useCheckout } from "@itemshop/sdk/react";

export function App() {
  return (
    <StorefrontProvider /* options={{ shopId }} initialData={snapshot} */>
      <Shop />
    </StorefrontProvider>
  );
}

function Shop() {
  const { data: shop } = useShop();
  const { products, loading, error } = useProducts();
  const cart = useCart();                            // zapisany w localStorage, synchronizowany między kartami
  const { checkout, loading: paying, error: checkoutError } = useCheckout();

  if (loading) return <Spinner />;
  if (error) return <p>{error.message}</p>;
  return (
    <>
      <h1>{shop?.displayName}</h1>
      {products.map((p) => (
        <button key={p.id} onClick={() => cart.add(p)}>{p.name} — {p.price} {p.currency}</button>
      ))}
      <p>{cart.count} szt., {cart.subtotal} {shop?.currency}</p>
      <button
        disabled={paying || cart.isEmpty}
        onClick={() => checkout({ nick, consents: { terms: true, immediateDelivery: true }, paymentMethodCode: "imoje", paymentChannel: "blik" })}
      >
        Zapłać
      </button>
      {checkoutError && <p role="alert">{checkoutError.message}</p>}
    </>
  );
}
```

* Hooki współdzielą jedną pamięć podręczną na providera. Podczas SSR nic nie jest pobierane, a koszyk renderuje się pusty na serwerze i w hydracji.
* `useCart()` zwraca `items`, `count`, `subtotal`, `isEmpty`, `add(product, qty, variant)`, `remove(productId, variant?)` i `clear()`. Koszyk mieści do 20 pozycji, a `checkout()` wysyła je wszystkie jako jedną grupę płatności.
* `useCheckout()` bierze linie koszyka, wysyła jeden `Idempotency-Key` na próbę (podwójne kliknięcie nie utworzy dwóch zamówień), przekierowuje do bramki (`redirect: false` wyłącza) i udostępnia `validateDiscount(code)`.
* Dodatkowo: `useRedeemVoucher()`, `useProduct(slug)`, `usePaymentMethods()`, `useOrderStatusByNumber({ number, key })`, `useRecentPurchases()`, `useTopSpenders()`, `useMonthlyGoal()`, `useOnlinePlayers()`, `useStorefront()` (surowy klient) i `useStorefrontQuery(key, fn)`.
* `initialData` przyjmuje migawkę z buildu (`await sf.snapshot()` czyta `/itemshop.data.json`).

## Klient API (z kluczem)

Tylko po stronie serwera — `createClient` odmawia startu w przeglądarce (klucz byłby publiczny), chyba że podasz `dangerouslyAllowBrowser: true`. Uprawnienia klucza: [API z kluczem](https://andcze.mintlify.site/pl/developers/api-keys.md).

```ts theme={null}
import { createClient } from "@itemshop/sdk/client";

const api = createClient({ apiKey: process.env.ITEMSHOP_API_KEY! });

const products = await api.products.list(shopId, { active: true });
await api.products.update(shopId, products[0]!._id!, { price: 24.99 });

// listy ze stronicowaniem: jedna strona (await) albo wszystko (for await)
const page = await api.orders.list(shopId, { status: "paid", limit: 50 });
for await (const order of api.orders.list(shopId, { status: "paid" })) console.log(order.orderNumber);

// dowolny udokumentowany endpoint, typowany z OpenAPI (ścieżka bez /api/v2)
const stats = await api.request("get", "/stats/{shopId}/dashboard", { path: { shopId }, query: { period: "30d" } });

// strona: upload → build → podgląd → produkcja
const revision = await api.site.upload(shopId, zipBytes, { message: "Nowy hero" });
const build = await api.site.builds.create(shopId, revision.id);
const done = await api.site.builds.wait(shopId, build.id);
const preview = await api.site.deployments.create(shopId, done.id, "preview");
await api.site.deployments.promote(shopId, preview.id);

// dostawa cyfrowa: pula kluczy produktu license_keys
await api.digital.addKeys(shopId, productId, { keys: ["AAAA-BBBB-CCCC-DDDD"] });   // także `text` (linia = klucz) albo `csv`; duplikaty pomijane
const pool = await api.digital.listKeys(shopId, productId, { status: "available" }); // zamaskowane wartości + statystyki
await api.digital.resend(shopId, orderId, { renewDownload: true });                 // ponowna wysyłka e-maila (5/h na zamówienie)

// AI Workspace: agent, grupy narzędzi MCP, tokeny, aktywność
await api.workspace.update(shopId, { agentId: "claude-code", mcp: { toolGroups: { orders: "read" } } });
const { token } = await api.workspace.tokens.create(shopId, { name: "Claude Code", scopes: ["site:read", "site:write"] }); // widoczny raz
const setup = await api.workspace.agentSetup(shopId, "codex"); // { steps, mcpConfig, instructions, … }
```

Klient obejmuje m.in. `products`, `orders`, `stats`, `apiKeys`, `templates`, `site` (`files`, `draft`, `revisions`, `upload`, `builds`, `deployments`, `ai`), `workspace`, `agents`, `skills`, `scopes`.

## Błędy, ponowienia, anulowanie

Każda porażka to `ItemShopError`:

```ts theme={null}
error.code    // "LEGAL_CONSENT_REQUIRED" (z backendu) | "NOT_FOUND" | "RATE_LIMITED" | "NETWORK_ERROR" | "TIMEOUT" | "ABORTED" | …
error.status  // status HTTP, 0 gdy nie było odpowiedzi
error.message // tekst dla człowieka — rozgałęziaj po `code`, nie po nim
error.details, error.retryAfter, error.body, error.isRetryable, error.isAbort
```

* **Ponowienia**: GET/HEAD i żądania z `Idempotency-Key` ponawiają błędy sieci, timeouty oraz 408/425/429/5xx (2 ponowienia, wykładniczy backoff z jitterem, `Retry-After` do 5 s). Pozostałe żądania ponawiają tylko 429. Konfiguracja: `retry: { retries, baseDelayMs, maxDelayMs }` lub `retry: false`.
* **Idempotencja**: `checkout.create` wysyła świeży klucz automatycznie; własny podasz w `idempotencyKey`.
* **Timeouty**: 30 s na próbę (`timeoutMs`), upload 5 min.
* **Anulowanie**: każda metoda przyjmuje `{ signal }` jako ostatni argument.

```ts theme={null}
const controller = new AbortController();
sf.products.list({}, { signal: controller.signal });
controller.abort(); // odrzuca z code "ABORTED"
```

Wspólne opcje obu fabryk: `apiBase` (domyślnie `https://itemshop.dev/api/v2`), `fetch`, `headers`, `timeoutMs`, `retry`. Kody błędów i limity: [Błędy i limity](https://andcze.mintlify.site/pl/developers/errors-and-limits.md).

## Webhooki

ItemShop nie wysyła podpisanych webhooków — zob. [Webhooki](https://andcze.mintlify.site/pl/developers/webhooks.md) (Discord) oraz [protokół pluginu](https://andcze.mintlify.site/pl/developers/plugin-protocol.md) dla realizacji zamówień w czasie rzeczywistym.

## CLI

```bash theme={null}
npx itemshop login                         # klucz API → ~/.config/itemshop/config.json (0600)
npx itemshop init my-shop --template bento # z itemshop-templates
cd my-shop && npm install
npx itemshop dev                           # Vite projektu z window.__ITEMSHOP__
npx itemshop deploy                        # zip → upload → build → adres podglądu
npx itemshop deploy --prod                 # … i promocja na produkcję
npx itemshop pull ./backup --rev draft     # pobranie plików strony
npx itemshop skills add itemshop-theme     # skill dla agenta
npx itemshop mcp config --agent codex      # konfiguracja MCP: claude-code | codex | opencode | cursor | gemini
```

Pełny opis: [CLI](https://andcze.mintlify.site/pl/site/cli.md). Masz własny Vite? Dodaj wtyczkę zamiast `itemshop dev`:

```ts vite.config.ts theme={null}
import { itemshop } from "@itemshop/sdk/vite";
export default defineConfig({ plugins: [react(), itemshop({ shopId: "…" })] }); // albo ITEMSHOP_SHOP_ID
```

> **[CLI](https://andcze.mintlify.site/pl/site/cli.md)**
>
> `init`, `dev`, `deploy`, `pull`.

> **[Storefront API](https://andcze.mintlify.site/pl/developers/storefront-api.md)**
>
> Endpointy pod spodem SDK.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.