Build a custom storefront

Run your own storefront UI on Kentro's storefront infrastructure: buyer sign-in, catalog, cart, quote, checkout, orders and account.

A custom storefront replaces the rendering of a Kentro storefront with your own web application, while the catalog, prices, carts, checkout, orders and buyer accounts stay in Kentro. Your application's server calls the Store API for the buyer in front of it, the same way Kentro's own storefront templates do.

Who this is for

  • Teams that need a design, a framework or a buying flow beyond the templates Kentro OS offers under Storefronts.
  • Agencies building B2B ordering sites for Kentro tenants.

How it fits

The browser talks only to your server. Your server holds the API key and, for each browser session, the buyer's tokens or a guest token, and calls the Store API and Kentro's authentication service. Payment happens with the store's payment provider: checkout answers a hosted page URL or a client secret, and your server confirms the result when the buyer returns.

 Browser                Your storefront server                   Kentro
 (pages, your cookie)   (API key; buyer or guest
                         tokens per session)

 sign in    ----------> POST /auth/login/customer  ------------>  authentication
 browse     ----------> POST /catalog.list         ------------>  Store API
 add to cart ---------> POST /cart.add (expectedRevision) ----->  Store API
 check out  ----------> POST /checkout.start       ------------>  Store API
            <---------- redirectUrl or clientSecret
 pays with the provider, returns
            ----------> POST /checkout.confirm     ------------>  order placed

🚧

Server side only

The browser never calls the Store API and never sees the API key, a buyer token or a guest token. The Store API answers with no CORS headers for tenant origins.

Prerequisites

  1. A published storefront in Kentro OS (Storefronts, then Publish), on its managed address or a verified custom domain. A storefront that is not published answers 404 NOT_FOUND for its host.
  2. An API key whose permission group holds storefront:serve. X-API-Key carries the key's secret.
  3. The storefront's host, sent as X-Storefront-Host on every call.

Objects involved

ObjectOperations
Storestore.get: branding, access rules, capabilities, the sign-up policy.
Sessionsession.get, session.guest, session.signup, account.confirm.
Catalogcatalog.categories, catalog.list, catalog.get, catalog.suggest, priced at the buyer's price list.
Cartcart.get, cart.add, cart.addMany, cart.set, cart.remove, cart.update, cart.contact, with revision and ownershipVersion on every answer.
Quotecart.get { "quote": true }: shipping, tax, credits, the amount to charge, and quoteFingerprint.
Checkout attemptcheckout.start, checkout.status, checkout.confirm, checkout.cancel.
Orders and invoicesorders.list, orders.get, orders.reorder; invoices.list, invoices.get, invoices.pay, invoices.payLink.
Account booksaccount.*, addresses.*, payments.* and carriers.*, each written with expectedVersion.
Subscriptions and applicationssubscription.* for recurring orders; application.* for buyer applications.

Sequence

  1. Read the store. store.get (store-get) on every page request: access, publicPrices, guestPrices, guestCheckout, paymentProvider, fulfillment, currency and acquisition decide what to render.
  2. Identify the visitor. A guest gets a token from session.guest (session-guest), only when guestCheckout is true. A buyer signs in with POST https://apis.kentro.live/auth/login/customer and { email, password, host }. A new buyer signs up with session.signup (session-signup) and confirms with account.confirm (account-confirm). session.get (session-get) says what the tokens the server holds amount to.
  3. Browse. catalog.categories (catalog-categories) answers the navigation tree; catalog.list pages the products the buyer may see; catalog.get (catalog-get) answers one product with its variants; catalog.suggest (catalog-suggest) is the typeahead, limited to 30 calls per 3 seconds per identity.
  4. Build the cart. cart.get (cart-get), then cart.add (cart-add), cart.addMany (up to 100 lines), cart.set (cart-set) and cart.remove (cart-remove). cart.update (cart-update) sets fulfillment, addresses, payment method, shipping rate, coupon and notes; a guest sends email and addresses with cart.contact (cart-contact). Every mutation sends expectedRevision from the last answer and answers the whole cart: render it, never recompute totals.
  5. Quote. cart.get { "quote": true } prices the cart and answers checkoutAllowed, issues, shippingRates, charge, balanceDue and quoteFingerprint. A quote lives at most 10 minutes.
  6. Start checkout. checkout.start (checkout-start) with expectedRevision, an idempotencyKey and the quoteFingerprint. The answer's state says what happens next: pending_payment or needs_action with a redirectUrl or clientSecret, complete with an orderId, or declined with the message to show.
  7. Finish checkout. When the buyer returns from the provider, checkout.confirm (checkout-confirm) verifies the payment and completes the order. checkout.status (checkout-status) polls an attempt; checkout.cancel (checkout-cancel) abandons one the provider confirms cannot complete.
  8. After the order. Orders, reorders, invoices and the account: orders.reorder (orders-reorder), invoices.pay (invoices-pay), account.get (account-get), addresses.save (addresses-save) and payments.save (payments-save). The bodies of every step are in A buyer's journey.
  9. Measure, optionally. pixel.get (pixel-get) answers the storefront's Kentro Pixel public key for the browser SDK; see Measure with Kentro Pixel.

Edge cases

CaseWhat the Store API answersWhat to do
A guest with a cart signs in409 CART_CHOICE_REQUIRED on the next cart mutationSend both tokens, read cart.choices, let the buyer pick, call cart.choose.
A stale cart in a second tab409 CONFLICT "Your cart changed. Review the current cart."Read cart.get, show the current cart, let the buyer act again.
An unapproved or unconfirmed buyer403 APPROVAL_REQUIRED or 403 CONFIRMATION_REQUIREDShow the pending-approval panel, or the confirmation step.
Prices hidden from this visitorprice is null; the cart answers pricesVisible: falseRender "sign in for prices" instead of zero.
An expired buyer token401 UNAUTHORIZED "Storefront authorization failed."Refresh once with POST /auth/refresh, then retry.
A lost checkout.start answercheckout.status { idempotencyKey, quoteFingerprint } finds the attempt; never start a second checkout (409 CHECKOUT_PENDING).
A declined payment200 with state: "declined" and a messageShow the message and return to the cart.
A product with variants added without one409 PRODUCT_VARIANT_REQUIREDSend variantId.
A browser whose session changed409 CONFLICT "Your store or account changed. Reload before continuing." with X-Expected-BuyerReload the session and start over.
A degraded store answerResponse header X-Store-Degraded: payment-configDo not cache that answer.

Checklist

  • Every Store API call comes from the server; the browser holds only your session cookie.
  • X-API-Key, X-Storefront-Host, and the buyer or guest token travel on every call; X-Expected-Buyer and X-Storefront-Client-Ip too.
  • Tokens refresh before expiry or on the first 401.
  • Every cart mutation sends expectedRevision, and the UI renders the cart the API answers.
  • Checkout sends an idempotencyKey and the current quoteFingerprint, and recovers through checkout.status.
  • Amounts are rendered from integers in minor units with the store's currency.
  • Nothing buyer-specific is cached: every answer carries Cache-Control: no-store.
  • The Go-live checklist passes.

Build it with an AI agent

The prompt below starts a coding agent on this storefront. Agent setup and the other prompts: Build with AI agents.

📘

Build it with an AI agent

Build a storefront whose server calls the Kentro Store API for catalog, buyer sign-in, cart, quote and checkout, in Next.js or any framework. Copy the prompt into Claude Code, Codex, Cursor or another coding agent. The prompt library explains how to run it.

You are building a custom storefront on the Kentro Store API. The user's own web application renders the pages, and its server calls the Store API for the buyer in front of it. Kentro keeps the catalog, prices, stock, buyer accounts, carts, quotes, checkout, orders and invoices. Use the framework the user names; default to Next.js with the App Router.

Read first. Fetch https://docs.kentro.io/llms.txt and these pages, and look up every operation and schema in them before you use it:
- https://docs.kentro.io/docs/ai-agent-instructions.md (rules you must follow)
- https://docs.kentro.io/reference/store-api-getting-started.md
- https://docs.kentro.io/reference/store-api-authentication.md
- https://docs.kentro.io/reference/store-api-conventions.md
- https://docs.kentro.io/reference/store-api-flows.md
- https://docs.kentro.io/docs/guide-custom-storefront.md

Ask the user for these before writing code:
1. The framework and where the application will run.
2. The host of a published storefront in the sandbox (Kentro OS at https://kentro.cc, `Storefronts`). It is the X-Storefront-Host value.
3. A sandbox API key whose permission group holds `storefront:serve`, stored server-side as KENTRO_API_KEY. Never in the chat, never in client code.
4. Which pages to build first: catalog, product, cart, checkout, account or orders.

Rules:
- Call the Store API only from the server: Server Components, Route Handlers or Server Actions in Next.js, server routes in other frameworks. No Client Component, browser script or NEXT_PUBLIC_ variable ever holds the key or a token. The API sends no CORS headers for browsers.
- Base URL https://apis.kentro.dev/store/v1 while building (production https://apis.kentro.live/store/v1); buyer sign-in at https://apis.kentro.dev/auth. Every operation is POST `/<family>.<operation>` with a JSON body, `{}` when it takes no fields.
- Every call sends X-API-Key and X-Storefront-Host (from configuration, lower case, no scheme). Buyer calls add X-Storefront-Buyer and guest calls add X-Storefront-Guest. Also send X-Storefront-Client-Ip and X-Expected-Buyer. The API does not read Authorization.
- Keep buyer and guest tokens on the server, keyed by an httpOnly session cookie. Refresh on 401 with POST /auth/refresh.
- Money is integers in minor units; format it with the store's currency.
- Every cart mutation sends expectedRevision and ownershipVersion from the last cart answer. On 409 CONFLICT, read cart.get again and re-render.
- checkout.start sends idempotencyKey, quoteFingerprint and expectedRevision. Never start a second checkout while one is pending.
- Never cache Store API answers across requests. Call only the operations the Store API reference documents. There is no Kentro SDK: write a small server-only client module.

Steps:
1. A server-only Kentro client: one function that posts to an operation with the headers above and turns refusals (`{ code, message, fieldErrors }`) into typed errors.
2. store.get on each request; render according to access, publicPrices, guestCheckout, fulfillment and currency.
3. Catalog and product pages with the catalog operations on the flows page.
4. Buyers: sign in with POST https://apis.kentro.dev/auth/login/customer `{ email, password, host }`, sign out with POST /auth/logout, sign up with session.signup, and call account.confirm when session.get answers confirmed: false. Guests: session.guest when store.get answers guestCheckout: true.
5. Cart: cart.get, cart.add, cart.set, cart.remove, and cart.update for buyers or cart.contact for guests, plus the cart choice after sign-in when both carts hold items.
6. Quote and checkout: cart.get `{ "quote": true }`, then checkout.start. When state is pending_payment or needs_action, send the buyer to redirectUrl and call checkout.confirm when they return; otherwise poll checkout.status.
7. Orders: orders.list and orders.get.
8. On every refusal, show the buyer the message from the answer; it is written for buyers.

Acceptance checks. Run each one against the sandbox and show the user the requests and responses, with secrets redacted:
- store.get answers 200 for the host; a request without X-Storefront-Host answers 400 VALIDATION.
- A cart.add with a stale expectedRevision answers 409 CONFLICT, and the application re-reads the cart instead of failing.
- cart.get `{ "quote": true }` answers quoteReady: true with a quoteFingerprint for a cart with an address and a payment method.
- checkout.start reaches pending_payment or complete; sending it again with the same idempotencyKey does not create a second order, and checkout.status `{ idempotencyKey, quoteFingerprint }` finds the attempt.
- The signed-in buyer sees the new order in orders.list.
- The production build's client assets contain neither the API key's secret nor the string X-API-Key.

Did this page help you?