> ## 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.

# Storefront API

> The public v1 API for shop sites: shop, products, payment methods, discounts, checkout, vouchers and widgets — no key required.

Storefront API v1 is the public API your shop's site uses in the browser. It needs no key and accepts no secrets — the shop is identified by `shopId` or domain. Responses are cached on the CDN, and CORS allows requests from shop domains and preview addresses.

> [!TIP]
>
> On the site, use the [SDK](https://andcze.mintlify.site/en/developers/sdk/typescript.md) — `createStorefront()` sets the `shopId`, API address and language for you. Raw endpoints are handy in other languages and for debugging.

## Endpoints

Base URL: `https://itemshop.dev/api/v2/storefront/v1`

| Method | Path | Description | SDK |
| - | - | - | - |
| `GET` | `/shops/:shopIdOrDomain` | Shop: name, logo, currency, languages, servers (online), links, terms | `sf.shop.get()` |
| `GET` | `/shops/:shopId/products` | Products with categories, variants, bundles, prices after discounts | `sf.products.list()` |
| `GET` | `/shops/:shopId/products/:slugOrId` | A single product | `sf.products.get()` |
| `GET` | `/shops/:shopId/payment-methods` | Code, name, logo, amount limits | `sf.paymentMethods.list()` |
| `POST` | `/shops/:shopId/discounts/validate` | Check a discount code for a cart | `sf.discounts.validate()` |
| `POST` | `/shops/:shopId/checkout` | Create an order (1–20 lines) → `{ orderId, orderNumber, statusKey, redirectUrl, … }` | `sf.checkout.create()` |
| `POST` | `/shops/:shopId/vouchers/redeem` | Redeem a voucher → `{ orderNumber, statusKey, status, productName }` | `sf.vouchers.redeem()` |
| `GET` | `/orders/by-number/:orderNumber/status?key=` | Order or group status by number and status key | `sf.orders.statusByNumber()` |
| `GET` | `/orders/by-number/:orderNumber/delivery?key=` | Collect digital content (keys, file, message) | `sf.orders.delivery()` |
| `GET` | `/downloads/:token` | Download a file from a signed link | — |
| `GET` | `/orders/:orderId/status` | **Deprecated** — pre-3.0 orders only | `sf.orders.status()` |
| `GET` | `/shops/:shopId/widgets/recent-purchases` | Recent purchases | `sf.widgets.recentPurchases()` |
| `GET` | `/shops/:shopId/widgets/top-spenders` | Top spenders | `sf.widgets.topSpenders()` |
| `GET` | `/shops/:shopId/widgets/monthly-goal` | Monthly goal | `sf.widgets.monthlyGoal()` |
| `GET` | `/shops/:shopId/widgets/online-players` | Online players | `sf.widgets.onlinePlayers()` |

## Example: product list

```bash theme={null}
curl https://itemshop.dev/api/v2/storefront/v1/shops/65f1c0ffee0000000000abcd/products
```

## Checkout

```bash theme={null}
curl -X POST https://itemshop.dev/api/v2/storefront/v1/shops/<shopId>/checkout \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7f9c2b1e-4d3a-4b8e-9c61-2a5f0e8d1c34" \
  -d '{
    "items": [{ "productId": "<productId>", "quantity": 1 }],
    "nick": "Steve",
    "email": "steve@example.com",
    "paymentMethodCode": "blik",
    "discountCode": "SUMMER10",
    "consents": { "terms": true, "immediateDelivery": true, "legalVersion": "<shop legal.version>" },
    "returnUrl": "https://sklep.megacraft.pl/thank-you"
  }'
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "orderId": "664f1a2b3c4d5e6f7a8b9c0d",
    "orderNumber": "A1B2C3D4",
    "statusKey": "k7m2q9x4tz",
    "status": "pending",
    "groupId": null,
    "orderIds": ["664f1a2b3c4d5e6f7a8b9c0d"],
    "redirectUrl": "https://pay.imoje.pl/…",
    "free": false
  }
}
```

Checkout rules:

* **A cart is 1–20 lines** in `items`; more is `400 CART_TOO_LARGE`. One line is a regular order. Two or more form a **payment group**: one transaction at the provider for the total, with a separate order per line inside. The response then has `groupId` and `orderIds[]`, and `orderId` and `orderNumber` point to the group. A discount code is applied once for the whole cart, and a payment method's min/max limits are checked against the total.
* **Nick or e-mail**: a `game` shop requires `nick` (3–16 characters `a-zA-Z0-9_`, otherwise `NICK_REQUIRED`); a general shop requires `email` (otherwise `EMAIL_REQUIRED`). A product with digital delivery (`deliveryKind` other than `commands`) always requires `email`, even in a `game` shop.
* **Consents are required**: `terms` (the terms) and `immediateDelivery` (a combined consent to immediate delivery and loss of the right of withdrawal), plus `legalVersion` — the version of the documents from `GET /shops/:shopIdOrDomain` that you showed the buyer. If the shop changes its terms, checkout returns `LEGAL_DOCUMENTS_CHANGED` — fetch the shop again and show the new documents.
* **`Idempotency-Key`** (e.g. a UUID) is required. Retrying with the same key won't create a second order.
* **`returnUrl`** must point to the shop's domain or a preview address (otherwise `RETURN_URL_NOT_ALLOWED`). After payment the buyer returns to it with `?itemshop_order=<number>&itemshop_key=<status key>` (any `#…` fragment is dropped) — read them from `location.search` and fetch the status by number and key. In production `localhost`, `127.0.0.1` and `[::1]` are rejected (also as `Origin`). Without `returnUrl` the buyer lands on the shop's hosted status page.
* **A free order** (100% discount) has `free: true` and a `redirectUrl` that leads straight to the status page.
* **The payment method** is the `code` from `/payment-methods` — a shop can have several methods from the same provider.
* **Bot protection** is adaptive: it doesn't block regular purchases, and checkout is also protected by a rate limit and duplicate-request blocking.
* The site **does not process payments** — it only redirects to `redirectUrl`.

## Errors

Every error has the same envelope with a machine-readable `code` — translate the message in your UI based on it; the `error` field is for the developer only.

```json theme={null}
{
  "success": false,
  "statusCode": 404,
  "code": "SHOP_NOT_FOUND",
  "error": "Shop not found",
  "timestamp": "2026-10-27T12:00:00.000Z",
  "path": "/api/v2/storefront/v1/shops/my-shop"
}
```

| Code | When |
| - | - |
| `VALIDATION_FAILED` | Invalid data (details in `details`). |
| `DISCOUNT_INVALID` | The discount code doesn't exist, has expired or doesn't fit the cart. |
| `LEGAL_CONSENT_REQUIRED`, `LEGAL_DOCUMENTS_CHANGED` | Missing consents or an outdated document version. |
| `SHOP_PAYMENT_NOT_READY`, `PAYMENT_INIT_FAILED` | The shop has no working payment method / the gateway didn't respond. |
| `NICK_REQUIRED`, `EMAIL_REQUIRED`, `CART_EMPTY`, `CART_TOO_LARGE` | Missing nick/e-mail, an empty cart or more than 20 lines. |
| `PRODUCT_NOT_FOUND`, `PRODUCT_UNAVAILABLE`, `OUT_OF_STOCK`, `PRODUCT_VARIANT_REQUIRED`, `INVALID_PRODUCT_VARIANT`, `INVALID_QUANTITY` | A problem with the product, variant or quantity. |
| `VOUCHER_NOT_FOUND`, `VOUCHER_USED`, `VOUCHER_EXPIRED` | Unknown or disabled voucher (same answer on purpose), already redeemed (also while a redemption is in progress), or expired. All `400`. |
| `ORDER_NOT_FOUND` | Wrong number or status key. |
| `ORDER_NOT_PAID`, `DELIVERY_REVOKED`, `NO_DIGITAL_DELIVERY` | Collecting content: order not paid (`409`), refunded or disputed (`410`), no digital content (`404`). |
| `DOWNLOAD_EXPIRED`, `DOWNLOAD_LIMIT_REACHED`, `DOWNLOAD_UNAVAILABLE` | The file link expired, downloads are used up or the file is unavailable (`410`). |
| `RETURN_URL_NOT_ALLOWED` | `returnUrl` outside the shop's domains. |
| `CAPTCHA_REQUIRED` | Bot protection requires verification. |
| `CHECKOUT_IN_PROGRESS`, `IDEMPOTENCY_KEY_REUSED` | The same purchase attempt is already in progress / the key was used with different data. |
| `ORIGIN_NOT_ALLOWED` | A request from a domain that doesn't belong to the shop. |
| `RATE_LIMITED` | Too many requests — wait a moment. |

## Redeeming a voucher

`POST /shops/:shopId/vouchers/redeem` with `{ code, nick }` and an optional `email` (an address for digital content; also `turnstileToken` or the `cf-turnstile-response` header) returns `{ orderId, orderNumber, statusKey, status, productName, serverName, nick }`. A voucher creates a free order delivered like a normal purchase.

## Order status

`GET /orders/by-number/:orderNumber/status?key=<statusKey>` returns `{ orderId, orderNumber, shopId, status, productName, serverName, nick, amount, currency, createdAt, groupId, items[], delivery }`. The number can point to an order or a group; for a group `status` is aggregated (it can be `partially_refunded`) and `items[]` lists the lines (`orderId`, `orderNumber`, `status`, `productName`, `variant`, `quantity`, `serverName`, `amount`). A wrong or missing key is `404 ORDER_NOT_FOUND`. In a general shop `nick` is a masked e-mail. Orders created since 3.0 have a 10-character `statusKey` — it comes back from checkout and voucher redemption and in the return URL. The old `GET /orders/:orderId/status` works only for pre-3.0 orders.

## Collecting digital content

When `delivery.ready` in the status is `true`, fetch the content: `GET /orders/by-number/:orderNumber/delivery?key=<statusKey>` (20/min/IP, `no-store`). The response is `{ orderNumber, items[] }`; a line has `kind` (`license_keys`, `download`, `text`), `state` (`pending`, `awaiting_stock`, `delivered`, `revoked`), `keys[]`, `pendingUnits`, `text` and `download { url, fileName, expiresAt, maxDownloads, remaining, expired }`. Render `text` as plain text, never HTML. `awaiting_stock` with `pendingUnits > 0` means the key pool ran dry — keys also follow by e-mail. Each opening of `download.url` (`GET /downloads/:token`) uses one download. An unpaid order is `409 ORDER_NOT_PAID`, a refund or dispute `410 DELIVERY_REVOKED`. Seller-side setup: [Digital content delivery](https://andcze.mintlify.site/en/shop/digital.md).

## Widgets and privacy

In the shop settings, the owner can turn off public data (recent purchases, top spenders, monthly goal, online players). A disabled widget returns an **empty list** for everyone — the site should then hide the section.

## Cache and limits

* Catalog data is cached on the CDN for a short time — a price change in the panel shows up on the site after a moment.
* Checkout and vouchers have a per-IP request limit. See [Errors and limits](https://andcze.mintlify.site/en/developers/errors-and-limits.md).

> **[TypeScript SDK](https://andcze.mintlify.site/en/developers/sdk/typescript.md)**
>
> A convenient layer over this API.

> **[API with a key](https://andcze.mintlify.site/en/developers/api-keys.md)**
>
> Managing the shop from a server.


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