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

# Create a shop without an account

> One unauthenticated request gives you a real Quickbutik shop with a catalog, a cart and a hosted checkout. Build on it, then hand it over to the merchant by email.

You don't need an account, an email or a card to start building. One request to `POST /v2/shops` creates a real shop and returns everything you need: a token for building the catalog and a publishable key for the storefront. When the shop has proven worth keeping, the merchant claims it by email. From then on they own it, it gets 14 days free, and payments with Swish, Klarna, Vipps MobilePay, iDEAL, Apple Pay, Google Pay and cards can be switched on. Orders, receipts, VAT and bookkeeping are then run from the Quickbutik admin or the iOS and Android app.

```text theme={null}
1. CREATE     POST https://api.quickbutik.com/v2/shops   (no token)   →  shop + three credentials
2. BUILD      manage the catalog with the PERSONAL ACCESS TOKEN        →  server-side only
3. SELL       build the storefront with the PUBLISHABLE KEY            →  browser-safe
4. HAND OVER  POST /v2/shops/{id}/claim with the merchant's email      →  the merchant owns the shop
```

<Warning>
  An unclaimed shop **expires 72 hours after creation**. Plan the claim early and tell the merchant the deadline.
</Warning>

## Start here: which shop are you building on?

Decide from what's already in front of you, in this order:

<Steps>
  <Step title="You have a publishable key (qb_pk_…)">
    It's pasted in the prompt, in `.env` as `QUICKBUTIK_PUBLISHABLE_KEY`, or on a kit script tag. The shop exists and has a catalog. Build the storefront on it and skip this page. Start at the [Quickstart](/kit/quickstart).
  </Step>

  <Step title="You have a personal access token (qb_pat_…) or the Quickbutik MCP server">
    The shop exists. Manage the catalog within the token's scopes, and get the publishable key from **Custom storefront** in the admin. See [Publishable keys](/kit/publishable-keys).
  </Step>

  <Step title="You have neither">
    This is the normal case behind "build me a shop that sells X". **Create the shop now, before you write any page.** It takes one request and no account, and the response contains the keys. Follow the rest of this page.
  </Step>
</Steps>

<Info>
  **Building with an AI agent?** When the prompt and the project contain no key, the agent should create the shop right away. It shouldn't ask whether the user already has a shop or ask them for a key. A user who had a key would have pasted it, and creating a shop is faster than asking. Agents should read [quickbutik.com/agents.md](https://quickbutik.com/agents.md) at the start of every session.
</Info>

## Before you create anything

* **One shop per project.** Look for `QUICKBUTIK_SHOP_ID`, `QUICKBUTIK_PUBLISHABLE_KEY` or `QUICKBUTIK_PAT` in the project's `.env` files first. If they exist, reuse them. Never create a second shop for a project that already has one, and never create shops to get around a limit.
* **Creation is rate limited** to 3 per hour and 10 per day per IP. A `429` carries `Retry-After`. Stop and report it instead of retrying in a loop.
* **Don't ask for the merchant's email yet.** It's only needed at the [claim step](#4-hand-the-shop-over).

## 1. Create the shop

The endpoint needs no authentication. If you send an `Authorization` header, it's ignored.

```bash theme={null}
curl -X POST https://api.quickbutik.com/v2/shops \
  -H "Content-Type: application/json" \
  -d '{"name": "Nordic Ceramics", "country": "SE"}'
```

| Field | Required | Value |
| - | - | - |
| `name` | Yes | The shop name, 1 to 50 characters. |
| `country` | No | `SE` (default), `DK` or `NO`. Sets currency, VAT and language: SEK/sv, DKK/da or NOK/no. |
| `include_storefront` | No | `true` gives the shop a hosted Quickbutik storefront and returns its URL and preview password. Default `false`: the shop only has the frontend you build. |

Response `201`:

```json theme={null}
{
  "id": "100200300A",
  "name": "Nordic Ceramics",
  "status": "unclaimed",
  "expires_at": "2026-01-04T12:00:00.000Z",
  "personal_access_token": "qb_pat_100200300A_…",
  "pat_scopes": ["products:read", "products:write"],
  "publishable_key": "qb_pk_100200300A_…",
  "publishable_key_scopes": ["storefront:read", "products:read", "cart:read", "cart:write", "checkout:read", "checkout:write"],
  "claim_token": "…a 64-character token, shown once…"
}
```

With `include_storefront: true`, the response also carries `storefront_url` (for example `https://nordic-ceramics.quickbutik.com`) and `storefront_password`. The hosted storefront starts out password protected and can be made public after the claim. It can take up to a minute to render after creation. Nothing you build with the kit depends on it, and the merchant can add one later from the admin.

### Store the three credentials correctly

This is the most important part of this page.

| Credential | Where it may live | What it does |
| - | - | - |
| `personal_access_token` (`qb_pat_…`) | **Server-side only**: a gitignored `.env` or server code. Never a browser bundle, a public repo or a log. | Creates and edits products, variants, images and categories. Its scopes are `products:read` and `products:write` only, so it can't read orders or change settings. |
| `publishable_key` (`qb_pk_…`) | Safe in frontend code and public env vars, like a Stripe publishable key. | Reads the visible catalog, runs carts, opens the hosted checkout and reads confirmations. |
| `claim_token` | **Server-side only, and keep it.** It's shown once and can never be retrieved again. | Needed once, at the claim step. If you lose it, the shop can never be handed over. |

Use these env names, so the next session and the kit find them:

```bash .env theme={null}
QUICKBUTIK_SHOP_ID=100200300A
QUICKBUTIK_PUBLISHABLE_KEY=qb_pk_…        # or NEXT_PUBLIC_… / VITE_… for the frontend
QUICKBUTIK_PAT=qb_pat_…                   # server-side only
QUICKBUTIK_CLAIM_TOKEN=…                  # server-side only
QUICKBUTIK_SHOP_EXPIRES_AT=2026-01-04T12:00:00.000Z
```

Then tell the merchant that the shop exists and is a test shop until it's claimed. Include when it expires, where the keys are, that no payment can be taken until they claim it and activate payments, and that claiming starts 14 days free.

## 2. Build the catalog

Catalog calls go to `https://api.quickbutik.com/v2` with the personal access token, from a server or a script and never from the browser. Use `/v2` only: the same host serves an older `/v1` API that answers `401 Unauthenticated` to this token.

```bash theme={null}
curl -X POST https://api.quickbutik.com/v2/products \
  -H "Authorization: Bearer $QUICKBUTIK_PAT" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8d2f1c0e-3b7a-4d5e-9f10-6a1b2c3d4e5f" \
  -H "X-Source-Interface: my-tool-name" \
  -d '{
    "title": "Handmade Ceramic Mug",
    "price": 249,
    "visible": true,
    "description": "A handmade ceramic mug, glazed in ocean blue."
  }'
```

Three things that save you a debugging session:

<AccordionGroup>
  <Accordion title="Merchant prices are in major units" icon="coins" defaultOpen>
    In the merchant API, `249` means 249.00 SEK, in both requests and responses. The storefront API and the kit return the same product in **minor units**: `24900` öre. Divide by 100 or use `formatMoney` when you display it. See [Currencies](/kit/concepts/currencies).
  </Accordion>

  <Accordion title="Products are hidden by default" icon="eye-slash">
    Products default to `visible: false`, and a hidden product never appears in the storefront API. Set `"visible": true` on everything the shop should sell, or the storefront will look empty.
  </Accordion>

  <Accordion title="Send an Idempotency-Key on every create" icon="repeat">
    Retries happen, and nobody wants duplicate products. Also send `X-Source-Interface: <your tool name>` (letters, digits, `-` and `_`, up to 50 characters). It shows up in the merchant's audit log, so they can see what created each product.
  </Accordion>
</AccordionGroup>

Variants (size, colour), images (ingested asynchronously from a public URL) and categories use the same token and the same host. A few well-made products are better than a large import. The merchant has 72 hours to decide whether the shop is worth claiming, and a working demo helps them decide faster than a big catalog.

## 3. Build the storefront

Storefront calls go to `https://commerce.quickbutik.com/v2` with the publishable key, from the browser or from a server. That's what the kit does. Pass the `publishable_key` from step 1 and follow the guide for your stack:

<CardGroup cols={2}>
  <Card title="Quickstart" icon="bolt" href="/kit/quickstart">
    A catalog, a cart and a checkout button in five minutes.
  </Card>

  <Card title="React and Next.js" icon="react" href="/kit/react/setup">
    `@quickbutik/kit/react`: provider, hooks and components.
  </Card>

  <Card title="Web components" icon="code" href="/kit/web-components/setup">
    `<qb-product>`, `<qb-cart>` and friends, for Vue, Svelte, Astro or a plain HTML page.
  </Card>

  <Card title="Vanilla JavaScript" icon="js" href="/kit/vanilla/setup">
    The typed client, for your own rendering or a server.
  </Card>
</CardGroup>

Payment always happens in the hosted Quickbutik checkout. Your storefront hands the cart over with `checkout.start()` and the shopper pays there. See [Checkout](/kit/concepts/checkout).

### Demo mode

Until the shop is claimed and payments are activated, the checkout runs in **demo mode**. The shopper walks through the real checkout (contact details, address, shipping and discount codes), but the payment step shows demo payment methods. No money moves and no order is created. `shop.get()` reports `demo.enabled: true`.

Nothing in your code changes when the merchant activates payments: the next handoff is live. See [Going live](/kit/concepts/going-live).

## 4. Hand the shop over

When the merchant wants to keep the shop, claim it before `expires_at`, using the `claim_token` from step 1 and the email the merchant gives you:

```bash theme={null}
curl -X POST https://api.quickbutik.com/v2/shops/100200300A/claim \
  -H "Content-Type: application/json" \
  -d '{"claim_token": "…the token from step 1…", "email": "merchant@example.com"}'
```

Response `202`:

```json theme={null}
{
  "status": "claim_email_sent",
  "email": "merchant@example.com",
  "link_expires_at": "2026-01-02T12:00:00.000Z"
}
```

Quickbutik emails a claim link to that address. The link is valid for 24 hours. Calling the endpoint again sends a fresh link, and only the newest one works. The merchant opens the link and confirms, and the shop becomes theirs. Everything you built carries over untouched, the 14 free days start, and they land in the Quickbutik admin. If the email already belongs to a Quickbutik account, the shop is added to that account.

You can't complete the claim on the merchant's behalf. Tell them to check their inbox and click the link.

### What changes after the claim

<CardGroup cols={3}>
  <Card title="Personal access token: revoked" icon="key">
    Ownership has moved to a verified person. Remove the token from `.env`. To keep managing the catalog, the merchant creates a new token under **Settings → API → API keys**, or connects the Quickbutik MCP server.
  </Card>

  <Card title="Publishable key: unchanged" icon="check">
    Storefront reads, carts and checkout keep working without interruption.
  </Card>

  <Card title="Checkout: demo until activated" icon="credit-card">
    The checkout stays in demo mode until the merchant activates **Quickbutik Payments** under **Settings → Payments**.
  </Card>
</CardGroup>

### Claim errors

| Status | Meaning |
| - | - |
| `400` | Invalid body. `claim_token` (1 to 128 characters) and a valid `email` are required. |
| `404` | Wrong token, or a token that doesn't belong to this shop id. |
| `410` | The shop expired or was already claimed. |
| `429` | The claim endpoint has its own limits: 10 per hour and 30 per day per IP. Wait for `Retry-After`. |
| `502` | The claim email couldn't be queued. It's safe to retry, and a successful retry sends a fresh link. |

## Lifecycle and limits

* An unclaimed shop expires 72 hours after creation. After `expires_at`, the personal access token stops working and the shop can no longer be claimed. Don't build on an expired shop. If a shop is still needed, create one new shop.
* Claiming removes the expiry.
* The catalog API works as soon as creation returns.
* Shop creation is limited to 3 per hour and 10 per day per IP. Claims are limited to 10 per hour and 30 per day per IP.

## Checklist

* [ ] `personal_access_token` and `claim_token` live only server-side. They're never rendered, logged or committed.
* [ ] The shop `id`, `claim_token` and `expires_at` are stored where the next session will find them.
* [ ] `qb_pk_` is the only Quickbutik key in client-side code.
* [ ] Prices are written in major units (merchant API) and displayed from minor units (kit).
* [ ] Every product the shop should sell has `visible: true`.
* [ ] Checkout goes through the hosted checkout, and the merchant knows it runs in demo mode until they claim the shop and activate payments.
* [ ] The merchant knows when the shop expires and how to claim it.
* [ ] Every `429` was handled with `Retry-After`, and shop creation was never retried in a loop.


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