# Stonebridge Commerce Storefront Developer Guide

This guide is for a store owner building their own store on the computer in
front of them. The admin and the Commerce API are hosted, so nothing but your
storefront runs on your machine.

One store means one project folder. If you own more than one store, give each
its own folder, its own key and its own deployment, and work through this guide
once per store. Nothing is shared between them.

It uses Next.js throughout, because that is what Claude builds when you follow
the quick start. The API itself can be called from any server-side framework if
you would rather write it yourself.

## Quick start: build your storefront with Claude Code

You do not have to write this store by hand, and you do not need to be a
developer. Claude Code is a coding agent that reads this guide and writes the
storefront for you. The rest of this document is the reference it works from,
and the reference you use when you want to check or change what it built.

The steps below assume you have never installed a developer tool in your life.

Budget about half an hour for steps 1 to 5. Step 6 is the part where you
describe the store you actually want.

### Step 1 — Install the Claude desktop app

This is the only thing you install by hand. Claude installs everything else for
you in step 2.

You need a Claude account with a paid plan. Claude Pro is the usual choice; Max
and Team also work. The free plan does not include Claude Code.

**On Windows**

1. Open your web browser and go to **https://claude.ai/download**.
2. Click the **Download for Windows** button. The file is called something like
   `Claude-Setup.exe` and lands in your **Downloads** folder.
3. Open the Downloads folder and double-click that file.
4. Windows may show a blue box saying *Windows protected your PC*. Click
   **More info**, then click **Run anyway**. This appears because the file is
   new to your computer, not because anything is wrong.
5. Wait for the installer to finish. Claude opens on its own when it is done.
6. Sign in with the email address on your Claude account.

**On a Mac**

1. Open Safari or Chrome and go to **https://claude.ai/download**.
2. Click the **Download for Mac** button. You get a file ending in `.dmg` in
   your **Downloads** folder.
3. Double-click that file. A small window opens showing the Claude icon next to
   an Applications folder.
4. Drag the Claude icon onto the Applications folder. That copies it in.
5. Open **Applications** and double-click **Claude**.
6. The first time, macOS asks whether you are sure you want to open it. Click
   **Open**.
7. Sign in with the email address on your Claude account.

**Find the Code tab**

Once Claude is open and you are signed in, look along the top of the window for
a tab called **Code**. Click it. That is Claude Code, and it is where everything
in this guide happens.

If you do not see a Code tab, your plan may not include it. Check your plan at
**https://claude.ai/settings/billing**.

### Step 2 — Let Claude install the developer tools

Building a store needs a few free programs: Node.js to run the site, Git to keep
its history, and the Google Cloud tools if you want to publish it to the
internet later. Do not go hunting for these. Claude installs them.

In the **Code** tab, click **New session**, choose any folder when it asks, and
paste this as your first message:

```text
I am setting up this computer to build an online store, and I have never done
this before. Please walk me through it and do the work where you can.

Check what is already installed, then install whatever is missing:
- Node.js, version 20 or newer
- Git
- The Google Cloud CLI (gcloud), which I will need to publish the store later
- Anything else you know a Next.js storefront needs

Use my operating system's normal package manager where there is one, and tell
me plainly before you install anything. If a step needs my password or needs me
to click something in a browser, stop and tell me exactly what to click.

I also need accounts. For each one, tell me what it is for, whether it costs
money, and walk me through creating it one screen at a time:
- A Google Cloud account, if publishing the store needs one
- Anything else that turns out to be required

When you are finished, show me the version number of each tool so I can see it
worked.
```

Claude asks permission before it installs anything, so nothing happens without
your say-so. Answer its questions as they come. Expect this to take ten to
twenty minutes, mostly waiting for downloads.

Claude cannot create accounts or type passwords for you. When it reaches one, it
stops and tells you what to do, then carries on once you say you are done.

### Step 3 — Create your store and a storefront key

In the merchant admin:

1. Register, verify your email and phone, and sign in.
2. Create a store, then select it.
3. Go to **Store management → API keys**.
4. Create a key of type **Storefront** and copy the value beginning
   `sb_storefront_`.

Section 5 covers keys in more detail. The one rule that matters most: this key
belongs on a server, never in browser JavaScript. Section 6 explains why, and
step 5 below tells Claude to respect it.

### Step 4 — Create a project folder

Make an empty folder for this store, somewhere you will find it again, and
start a Claude session in it. In the **Code** tab, click **New session** and
pick that folder.

Name it after the store. One folder holds one store, and a second store gets a
second folder, so the name is what tells them apart later.

Keep the store's key nearby but **do not paste it into the chat**. Claude will
tell you which file to put it in, where it stays out of your conversation
history.

### Step 5 — Give Claude its brief

Paste this in as your first message, replacing the two bracketed values:

```text
I want to build an online store using the Stonebridge Commerce platform.

Read these two references before writing any code:
- The storefront developer guide:
  https://stonebridge-admin-dev-633417163058.us-west1.run.app/docs/storefront-developer-guide.md
- The OpenAPI specification:
  https://stonebridge-api-dev-633417163058.us-west1.run.app/swagger/v1/swagger.json

My store sells: [DESCRIBE WHAT YOU SELL]
The look and feel I want: [DESCRIBE THE STYLE YOU WANT]

Build it as a Next.js storefront following the guide's patterns. Specifically:
- Keep my storefront key server-side only, in .env.local, and never in browser
  JavaScript or a NEXT_PUBLIC_ variable.
- Call the Stonebridge API from server-side code, using the browser-safe proxy
  route pattern in section 9 of the guide.
- Cover product listing, product detail, cart, and checkout.

Ask me for the storefront key when you need it, and tell me which file to put it
in rather than asking me to paste it into this chat.
```

Claude will fetch both references, plan the work, and start building. It asks
permission before creating or changing files, so you stay in control.

### Step 6 — Build, look, and refine

Ask it to start the store and show you, then describe changes in plain language:

- "The product grid should be three columns on desktop, one on mobile."
- "Add a search box to the header that filters the catalog."
- "The checkout button is too small and the contrast is poor."

Work in small steps and look at the result after each one. That gets you a store
you actually like far faster than one long instruction.

### What to do when something breaks

Paste the error into Claude — the full message, not a summary — and let it
diagnose. If it is an API error, section 16 lists what each status code means,
and Claude can read that section itself if you point it there.

The assistant in the admin dashboard can also answer questions about this
process and about your own store's data at any point.

## 1. The addresses you need

Four links. You do not install any of them.

| Service | URL |
| --- | --- |
| Merchant admin, where you manage the store | [https://stonebridge-admin-dev-633417163058.us-west1.run.app](https://stonebridge-admin-dev-633417163058.us-west1.run.app) |
| Commerce API, what your storefront calls | [https://stonebridge-api-dev-633417163058.us-west1.run.app](https://stonebridge-api-dev-633417163058.us-west1.run.app) |
| API reference | [https://stonebridge-api-dev-633417163058.us-west1.run.app/swagger](https://stonebridge-api-dev-633417163058.us-west1.run.app/swagger) |
| An example store, to see what one looks like | [https://willow-b-farm-dev-633417163058.us-west1.run.app](https://willow-b-farm-dev-633417163058.us-west1.run.app) |

These are development addresses. They can change before the platform launches,
so keep the API one in `.env.local` rather than typing it into your code.

## 2. Running your storefront on your own machine

The admin and the Commerce API are hosted. You do not install them, and you do
not need a database. The only thing that runs on your machine is the storefront
you are building, and it talks to the hosted API over HTTPS exactly as it will
once it is deployed.

From your storefront's folder:

```bash
npm run dev
```

That serves it at [http://localhost:3000](http://localhost:3000). Edit a file,
save, and the page reloads. Claude can start this for you and keep it running
while you describe changes.

Two settings point it at the platform. Both belong in `.env.local`, which is
never sent to the browser and should never be committed:

```text
STOREFRONT_API_ORIGIN=https://stonebridge-api-dev-633417163058.us-west1.run.app
STOREFRONT_API_KEY=sb_storefront_...
```

Section 7 covers these two in full, and section 6 explains why the key has to
stay on the server.

Your store's data is the same data the admin shows. Add a product in the admin,
reload your local storefront, and it is there. There is no separate test
database to keep in step, and nothing to migrate.

If you run a second store, start it from its own folder on a different port,
with `npm run dev -- --port 3001`. Each folder keeps its own `.env.local`, so
the two never reach for each other's key.

## 3. Where your storefront sits

You build the storefront. Everything to its right is hosted and is not yours to
run, install or configure:

```text
Customer browser
      |
      v
Storefront application
  - renders pages
  - keeps the storefront key on the server
  - proxies cart and checkout requests
      |
      | HTTPS + X-Storefront-Key
      v
Stonebridge Commerce API   <- hosted
      |
      v
Your store's data          <- hosted
```

Two rules follow from that shape. Your storefront never opens a database
connection, and it never lets a browser tell it which store to read: the key
already says which store it is.

## 4. Create an account and store

1. Open the [merchant admin](https://stonebridge-admin-dev-633417163058.us-west1.run.app).
2. Register with an email address and password.
3. Verify the email address and phone number.
4. Sign in.
5. Create a store from the dashboard.
6. Select the store before managing its catalog or settings.

The store you have selected is the one the dashboard and your keys apply to.
One account can own several stores; switch between them with **Switch store**
in the top bar. Each one gets its own project folder on your machine, so do not
point a second store at a storefront you already built.

## 5. Create a storefront key

In the selected store:

1. Open **Store management → API keys**.
2. Create a key with type **Storefront**.
3. Give it a descriptive name, such as `Production storefront`.
4. Copy the value beginning with `sb_storefront_`.

Storefront keys are intended for catalog, cart, and checkout traffic. Secret
automation keys beginning with `sb_test_` use the `X-Api-Key` header and should
not be substituted for storefront keys.

During development, keys can be viewed again in the dashboard. Production
security will eventually use stricter secret-display and rotation rules.

## 6. Keep the key on the server

Never put a storefront key in:

- a variable whose name begins with `NEXT_PUBLIC_`;
- browser JavaScript;
- source control;
- screenshots, logs, or support messages;
- a URL or query string.

Keep it in the storefront hosting provider's secret or environment-variable
configuration. Browser requests should call the storefront's own server route,
which adds the key when forwarding the request to Stonebridge.

## 7. Next.js environment

For local storefront development, create an ignored `.env.local` file:

```dotenv
STOREFRONT_API_ORIGIN=https://stonebridge-api-dev-633417163058.us-west1.run.app
STOREFRONT_API_KEY=sb_storefront_replace_with_your_key
```

For Cloud Run or another host, configure the same names as runtime environment
variables. Store `STOREFRONT_API_KEY` in the host's secret manager.

## 8. Server-side API helper

The following helper runs only on the Next.js server:

```ts
const apiOrigin = (
  process.env.STOREFRONT_API_ORIGIN ??
  "https://stonebridge-api-dev-633417163058.us-west1.run.app"
).replace(/\/$/, "");

const storefrontKey = process.env.STOREFRONT_API_KEY;

if (!storefrontKey) {
  throw new Error("STOREFRONT_API_KEY is not configured.");
}

export async function storefrontFetch<T>(
  path: string,
  init?: RequestInit,
): Promise<T> {
  const response = await fetch(`${apiOrigin}${path}`, {
    ...init,
    cache: "no-store",
    headers: {
      accept: "application/json",
      "x-storefront-key": storefrontKey,
      ...init?.headers,
    },
  });

  if (!response.ok) {
    throw new Error(`Stonebridge API returned ${response.status}.`);
  }

  return response.json() as Promise<T>;
}
```

Do not import this helper into a client component.

## 9. Browser-safe proxy route

Cart and checkout actions originate in the browser. Add a Next.js route handler
that forwards those requests and injects the key on the server:

```ts
const apiOrigin = process.env.STOREFRONT_API_ORIGIN!;
const storefrontKey = process.env.STOREFRONT_API_KEY!;

async function proxy(request: Request, path: string[]) {
  const incomingUrl = new URL(request.url);
  const targetUrl = new URL(
    `/api/storefront/${path.join("/")}${incomingUrl.search}`,
    apiOrigin,
  );

  const response = await fetch(targetUrl, {
    method: request.method,
    headers: {
      accept: request.headers.get("accept") ?? "application/json",
      "content-type":
        request.headers.get("content-type") ?? "application/json",
      "x-storefront-key": storefrontKey,
    },
    body:
      request.method === "GET" || request.method === "HEAD"
        ? undefined
        : await request.arrayBuffer(),
    cache: "no-store",
  });

  return new Response(response.body, {
    status: response.status,
    headers: {
      "content-type":
        response.headers.get("content-type") ?? "application/json",
    },
  });
}
```

The Willow B. Farm reference application contains a complete catch-all proxy at:

```text
storefront/willow-b-farm/src/app/api/storefront/[[...path]]/route.ts
```

## 9b. Website content

Not everything on a store is a product. Recipes, banners, about pages, and
galleries are **content**, managed in the admin under **Content** and read
through these endpoints:

| Endpoint | Returns |
| --- | --- |
| `GET /api/storefront/content/sections?tag=recipe` | Every active section carrying that tag |
| `GET /api/storefront/content/sections/{handle}` | One section |
| `GET /api/storefront/content/files?tag=spring-gallery` | Files carrying that tag |

Tags are the grouping mechanism. A merchant tags three sections `recipe` and the
storefront renders a recipe index; they tag six images `spring-gallery` and the
storefront renders a gallery. There is no separate collection object.

`bodyHtml` is sanitised by the API before storage against a narrow allowlist, so
it is safe to render. Only `active` content is returned; drafts stay invisible.

With `@astraion/storefront`:

```ts
import { getSections, sectionAttribute } from "@astraion/storefront";

const recipes = await getSections({ tag: "recipe" });
const cookTime = sectionAttribute(recipes[0], "cookTime");
```

`attributesJson` is a free-form JSON object where a merchant can keep the few
structured values HTML cannot express — a cook time, a serving count. Read it
with `sectionAttribute`, which returns `undefined` rather than throwing if the
merchant typed something malformed.

## 10. Storefront API overview

Open the hosted [OpenAPI reference](https://stonebridge-api-dev-633417163058.us-west1.run.app/swagger)
for the current request and response schemas.

Common storefront operations include:

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/api/storefront` | Read store identity and presentation settings |
| `GET` | `/api/storefront/products` | List published products |
| `GET` | `/api/storefront/products/{handle}` | Read a published product and its variants |
| `GET` | `/api/storefront/catalog/navigation` | List the types, categories, and tags that have visible products |
| `GET` | `/api/storefront/collections` | List published collections |
| `GET` | `/api/storefront/collections/{handle}` | Read a collection and its products |
| `POST` | `/api/storefront/carts` | Create a cart |
| `GET` | `/api/storefront/carts/{token}` | Read a cart |
| `PUT` | `/api/storefront/carts/{token}/items/{variantId}` | Set an item quantity |
| `DELETE` | `/api/storefront/carts/{token}/items/{variantId}` | Remove an item |
| `PUT` | `/api/storefront/carts/{token}/discount` | Apply or remove a discount |
| `POST` | `/api/storefront/carts/{token}/pricing` | Quote shipping, tax, and discounts for a destination |
| `POST` | `/api/storefront/carts/{token}/checkout` | Validate and begin checkout |

Use the schemas displayed in OpenAPI rather than copying old example payloads.
The API contract will continue to evolve during development.

## 11. Products and publication

Store owners manage products in the hosted admin dashboard.

- Draft products remain hidden from storefront endpoints.
- Active products are visible.
- Archived products are hidden.
- Variants are separate purchasable records.
- Inventory can be tracked by location and variant.
- Collections organize products for navigation and merchandising.
- `productType` is a merchant-defined reusable classification.
- `productCategory` and `productCategoryPath` come from the platform taxonomy.
- `tags` is an array of merchant-assigned merchandising labels.
- `templateKey` selects a storefront presentation; it is `default` unless the
  merchant assigns another lowercase, hyphenated key.
- Publication is sales-channel specific. Product and collection endpoints use
  the primary storefront channel unless the request includes
  `?channel={salesChannelHandle}`.

Product list entries include `productType`, `productCategory`, `templateKey`
and `tags`. Product detail additionally includes `productCategoryPath`.
Each variant also returns `taxable`, `inventoryPolicy`,
`inventoryManagement`, and its optional `fulfillmentService`.
Storefronts should treat unknown template keys as `default` so a new admin
selection never breaks rendering. A Next.js product page can resolve a template
without storing framework paths in the API:

```ts
const templates = {
  default: DefaultProductTemplate,
  "featured-product": FeaturedProductTemplate,
} as const;

const ProductTemplate =
  templates[product.templateKey as keyof typeof templates] ?? templates.default;
```

The API evaluates scheduled `publishAt` and `unpublishAt` timestamps when a
catalog request is made. A product can therefore be active in the admin while
remaining hidden from a particular channel until its scheduled release.

### Filtering and navigation

`GET /api/storefront/products` accepts these optional query parameters:

| Parameter | Meaning |
| --- | --- |
| `type` | Product type **handle**, not its display name |
| `category` | Category key; matches that category **and every descendant** |
| `tag` | Tag name, case-insensitive. Repeat it to require **all** of the tags |
| `search` | Case-insensitive match on title or vendor. Wildcards are matched literally |
| `sort` | `newest` (default), `price_asc`, `price_desc`, or `title` |

An unsupported `sort` returns `400`. Filters combine with AND, and all of them
respect the same channel and publication rules as an unfiltered list.

Do not hard-code facet values. Call `GET /api/storefront/catalog/navigation` for
the types, category branches, and tags that currently have visible products,
each with a `productCount`, then use the returned `key` as the filter value:

```json
{
  "productTypes": [{ "key": "preserves-9f2a1c04", "label": "Preserves", "productCount": 6 }],
  "categories": [
    {
      "key": "food-beverages",
      "label": "Food & Beverages",
      "fullName": "Food & Beverages",
      "level": 0,
      "parentKey": null,
      "productCount": 11
    }
  ],
  "tags": [{ "key": "seasonal", "label": "seasonal", "productCount": 4 }]
}
```

Category counts roll up their descendants, so a parent's count includes products
filed under its children. Facets with no visible products are omitted, so a
shopper is never offered a filter that returns nothing. A product's own
`productCategoryPath` ends with its category key, which is what links a product
page back into category navigation.

If a product is missing from a storefront, confirm that it is active, has an
active variant, belongs to the expected store, and has the intended inventory
configuration.

## 12. Cart persistence

The API returns an opaque cart token when a cart is created. Store that token in
the browser's local storage or a secure storefront cookie. Do not treat it as a
database identifier.

A typical flow is:

1. Create a cart.
2. Save its token.
3. Add or update variant quantities.
4. Reload the cart from the token when the customer returns.
5. Collect customer and delivery details.
6. Quote pricing for the delivery destination.
7. Submit checkout.
8. Redirect to an order-confirmation page.

Handle `400`, `404`, `409`, and `429` responses explicitly. A `409` commonly
means inventory or another resource state changed.

### Quoting shipping and tax

A cart alone has no destination, so its `shippingTotal` and `taxTotal` are zero.
Post the delivery address to `/api/storefront/carts/{token}/pricing` once the
address is complete to receive the same breakdown checkout will freeze:

```json
{
  "currencyCode": "USD",
  "lines": [
    { "variantId": 41, "lineTotal": 50.0, "discountAmount": 5.0, "taxAmount": 4.5 }
  ],
  "shippingLine": {
    "title": "United States",
    "originalPrice": 7.0,
    "discountedPrice": 7.0,
    "freeShippingThresholdApplied": false
  },
  "taxLines": [{ "title": "CA sales tax", "ratePercentage": 10.0, "amount": 5.2 }],
  "discountApplications": [
    { "code": "WELCOME10", "valueType": "percentage", "value": 10.0, "amount": 5.0 }
  ],
  "subtotal": 50.0,
  "discountTotal": 5.0,
  "shippingTotal": 7.0,
  "taxTotal": 5.2,
  "total": 57.2,
  "requiresShipping": true,
  "shippingAvailable": true
}
```

The endpoint is read-only: it creates no order and consumes no discount usage.
Re-quote whenever the destination changes, and only display a quote for the
address it was calculated for. When `shippingAvailable` is `false`, the cart
requires shipping and no active zone covers the destination, so checkout will be
rejected.

Custom attributes set on a cart item are copied onto the order line at checkout
and are visible to the merchant in the admin order detail.

## 13. The one credential your storefront uses

Your storefront authenticates with exactly one thing: the `X-Storefront-Key`
header, carrying the key from section 5.

The platform issues other credentials, for the admin dashboard and for server
automation. Your storefront needs none of them, and must never carry one.

## 14. Reading the API reference

The [OpenAPI page](https://stonebridge-api-dev-633417163058.us-west1.run.app/swagger)
lists every storefront endpoint with its parameters and responses. It is the
authority when this guide and the API disagree, and Claude reads it as part of
the brief in step 5.

The page's own **Try it out** buttons do not send the `X-Storefront-Key`
header, so use them to read the shape of a response rather than to call the
API. Test real calls from your storefront's server-side code.

Swagger is public on the development platform. Do not assume it stays public.

## 15. Deployment

A deployable storefront needs:

- a Node.js production build;
- `STOREFRONT_API_ORIGIN` set to the hosted Commerce API;
- `STOREFRONT_API_KEY` mounted from a secret manager;
- an HTTPS public URL;
- server-side proxy routes for browser-originated writes;
- no direct database connection.

Cloud Run is the current reference host, but storefronts may be deployed to any
provider that supports server-side Next.js and secure runtime secrets.

## 16. Troubleshooting

### `401 Unauthorized`

- Confirm the correct header name is `X-Storefront-Key`.
- Confirm the key is active and belongs to the intended store.
- Confirm the value has no surrounding quotes or whitespace.

### `404 Not Found`

- Confirm the product or collection is published.
- Confirm the handle is correct.
- Confirm the cart token has not expired.

### `409 Conflict`

- Refresh the cart or product data.
- Recheck inventory.
- Resubmit using the latest resource version when an endpoint uses optimistic
  concurrency.

### `429 Too Many Requests`

Honor the `Retry-After` response header and avoid rapid polling.

### Storefront works locally but not after deployment

- Verify both runtime environment variables are configured.
- Ensure the key was mounted as a runtime secret.
- Review the storefront host's server logs.
- Call the API health endpoint:
  [https://stonebridge-api-dev-633417163058.us-west1.run.app/health](https://stonebridge-api-dev-633417163058.us-west1.run.app/health).

## 17. Security checklist

- Keep your storefront key server-side.
- Never open a database connection from your storefront.
- Never let a browser tell your storefront which store to read.
- Use HTTPS.
- Validate quantities and money on the API.
- Treat prices returned to the browser as display values, not authority.
- Avoid logging tokens, keys, personal data, or payment information.
- Rotate a key immediately if it is exposed.
- Give every store, and every deployment of a store, its own key.

## 18. Reference implementation

The [Willow B. Farm storefront](https://willow-b-farm-dev-633417163058.us-west1.run.app)
is a working store built on this platform, and the closest thing to a finished
example. Open it and click around. It shows:

- server-rendered catalog pages;
- product and collection routes;
- a server-side API helper;
- a browser-safe API proxy;
- cart persistence;
- checkout;
- a recipe blog;
- deployment to Cloud Run.

Point Claude at that URL if you want your store to work the same way. Your
store still gets its own folder, its own key and its own deployment: nothing is
shared with it.
