Skip to content

Storefront integration

Most of these guides are written merchant-first (admin writes, role-gated). A storefront is the opposite: it's mostly public reads plus public checkout writes. Legacy storefronts place the order with POST /public/orders; M3 storefronts adopt checkout-session writes (create, reprice, bind) before payment/confirmation. This page sequences those paths and — more importantly — draws the one boundary that trips up every storefront: what cartonium can estimate today vs. what it actually persists on an order.

Anonymous catalog reads carry the tenant header (x-organization-slug) without a bearer token. Other public operations have their own credential requirements: checkout can require a customer session, and payment handoffs resolve their tenant from the checkout token. Follow the operation's contract rather than assuming that every /public/* call is anonymous. Paths are under /api/v1.

External storefront backends can alternatively fetch catalog data server-to-server from /api/v1/storefront/* with a tenant API key. Keep the key on the server only; do not send it from browser JavaScript. Those reads reuse the same public-safe item and collection projections as /api/v1/public/*, but the tenant is bound by the key rather than x-organization-slug.

Automatic data selection

Configure Production and Sandbox (Preview/Staging) addresses in Settings → Domains. Browsers send their current Origin automatically. In SSR, server actions and backend proxies, derive the storefront origin from the current incoming request and send it as x-storefront-origin on every upstream storefront call, including token-only checkout calls. Use the framework's validated host/proxy boundary; never copy an arbitrary caller-supplied mode.

Domains resolves the data and checks the tenant. The same code works on both addresses; no commerce-mode setting or duplicate Sandbox URL is needed. Missing or conflicting context is rejected. Never retry against Live or cache a resolved mode globally. See the request contract.

Independently deployed storefronts need this transport change before adopting the updated API. Saving Domains configures routing; it does not rewrite an external website's code.

Get your connection details

The connection-details handoff is rolling out through staging; confirm that it is available in your Admin environment. In merchant Admin, open Settings → API & webhooks → Connection details (/settings#connection-details). An Owner, Admin, or staff member with settings management permission can use Copy integration brief to share the current environment, tenant slug, API base URL and resolved hosted customer-account URL with the storefront developer.

The brief contains public connection values only. It does not contain an API key, merchant session, customer information or payment link. If a value is unavailable, refresh Settings or ask your operator to check that environment; do not construct a hosted URL from a deployment hostname. Keep Preview and Production connection details separate.

Choose the integration path that fits the storefront:

  • Hosted account pages: use the resolved account URL when adopting the hosted cartonium checkout account surface. It is the hosted address, not a custom account implementation or a signed action link.
  • Public API: send the tenant slug on documented tenant-resolving public routes. An API key is not needed for anonymous public reads.
  • Optional server integration: use a separately created, scoped API key only from the server. The integration brief does not create or grant a key.

For storefront return links, verify the host in Settings → Domains, then configure destinations in Settings → Checkout. During acceptance, follow the actual checkout return links and confirm their tenant, environment and destination. Copying the brief does not verify DNS, activate a tenant or prove checkout readiness.

The cart/checkout boundary (read this first)

Before a storefront adopts the M3 checkout-session flow, its cart total is only an estimate. The legacy POST /public/orders path persists line items only — its subtotalInCents is the sum of the lines, taxInCents is 0, and totalInCents equals the subtotal. Everything else — tax, shipping, coupons, auto-discounts, bundles — is a display-only estimate until the server-side checkout session is repriced and bound.

MechanismEstimable today (display-only)Binding at POST /public/ordersBound by checkout sessions (M3)
Line items + subtotalcart stateYes — items[], subtotalInCents—
Order totalsubtotal (± your own estimates)Yes — totalInCents == subtotalInCentstotal incl. provider-gated tax, discounts, and selected shipping
Shippingzone configuration from GET /public/shipping/zones; current options from checkout-session repriceNo — no shipping line persistedYes, for carts requiring shipping — bind locks the selected option's shippingInCents into the total
CouponsPOST /public/cart/apply-coupon (pure preview)No — not attachedusage counted at placement
Auto-discountsPOST /public/auto-discounts/calculateNo — not attachedapplied at checkout
Bundlescheck-bundles / items/:slug/bundlesNo — not attachedapplied at checkout
Taxcart display estimate only; hosted tax preview exists on staging, with BYO adoption pending acceptance/releaseNo — taxInCents is 0 on legacy ordersbind independently recalculates and persists the authoritative tax-inclusive total

The rule: a storefront should not treat cart-side tax, shipping, or discount previews as binding. For the legacy public order path, reconcile the final display to subtotalInCents or label the extras "estimated, finalized at checkout." For M3 checkout sessions whose carts require shipping, select from reprice's availableShippingOptions and read the tax- and shipping-inclusive total after bind. Treat any amount returned by reprice, including taxInCents=0, as provisional. The staged hosted tax-preview route is not a released BYO estimation contract.

See Taxes for the preview contract, selected tax policy, merchant readiness and the disclosure required when cartonium did not calculate tax, plus the pending hosted-preview boundary.

Checkout tax at bind depends on the merchant's checkout tax policy and provider readiness. Where enabled and ready, Stripe-backed tax uses the merchant's verified connected account, computes destination-based tax from the captured address, persists an internal tax snapshot, and returns public taxInCents / totalInCents values. A persisted stripe_tax policy fails closed when its required provider is unavailable; it does not silently charge zero tax. The separate manual_no_tax policy requires an explicit responsibility acknowledgement. A stored stripe_tax policy still requires an available provider for a fully discounted cart. Once that check passes, a fully discounted cart with no charged shipping needs no positive tax calculation.

Use the bound server total as authoritative. Neither a cart estimate nor a zero-tax reprice response proves that checkout owes no tax. Merchant setup and readiness are covered in the payments help guide.

The read/write sequence

A typical storefront wires these in roughly this order:

  1. Catalog — /guides/catalog. GET /public/items and /public/items/:slug for product + variant data (public reads return ACTIVE only).

  2. Collections & pages — /guides/collections-pages. GET /public/collections, /public/collections/:slug/items, and /public/pages for merchandising + CMS content.

  3. Inventory — /guides/inventory. GET /public/inventory/:itemId for stock state at the PDP and cart; it returns the item's publicly visible rows (non-public items and archived variants are filtered out; capped at 5000 rows with no truncation signal, #3090), and availableQuantity is the units sellable now. For a SALE item with variants, the line must name one exact live variantId; an item-level target is refused, including when only archived variant history remains. Omission or null targets the item-level row only for a genuinely variant-less item. Pick the row matching that valid line target and compare the requested quantity with its availableQuantity — that is the UX guard, not a guarantee: the read cannot reserve the unit.

    Session create is the per-cart stock check: POST /public/checkout/sessions checks server-repriced SALE quantities before persisting a session, including expanded components of composed products. A measured shortfall returns HTTP 409 with code: "RESOURCE_CONFLICT", details.reason: "INSUFFICIENT_INVENTORY", and details.lines[] naming every short stock target. No session is created on that refusal; correct the quantity in the storefront and submit create again. bind rechecks stock and returns the same shortfall shape while leaving the session recoverable. These reads reserve nothing; successful create or bind does not guarantee stock at confirmation. Counts describe exact item/variant units, or expanded component units for a composed selection, not package quantities.

    Checkout confirmation remains the final authority: it reserves inventory and rejects an oversell when the valid SALE stock target's exact (itemId, variantId) row exists. A missing exact row is not zero and passes the create and bind stock checks. If still missing at confirmation, the valid line is preserved as unbounded with no stock check (an item-level row does not cover a variant line), so an "availability unknown" state is the only guard that line gets. RENTAL and SERVICE lines are outside this guard entirely — rental capacity comes from the booking/availability path, and services have no stock. POST /public/availability/check remains the booking/rental date-range preflight (it requires startDate/endDate, matches RENTAL items only, and reports every SALE line as unavailable), so do not wire it into a retail cart.

  4. Pricing — /guides/pricing. The cart-math previews: POST /public/cart/apply-coupon, POST /public/auto-discounts/calculate, POST /public/cart/check-bundles, and the proactive GET /public/items/:slug/bundles. All display-only (see the boundary above).

  5. Shipping — /guides/shipping. Use GET /public/shipping/zones only for zone configuration and live-rate eligibility. For a cart with shippable lines on the M3 checkout-session path, create a session, call reprice with the complete address, render its full availableShippingOptions set, and send the selected option's rateId as shippingRateId on the next reprice and on bind. Bind locks that shipping charge before payment or confirmation.

  6. Checkout — /guides/byo-checkout. Use checkout sessions for the server-authoritative total and the cartonium Stripe Payment Element handoff. Create/read/reprice/bind calls use x-organization-slug; the payment-session handoff resolves its tenant from the checkout token and does not send that header. Follow the customer-identity requirements before binding or preparing payment for a verified customer's email. Render your own checkout UI or adopt hosted cartonium checkout; either way, the API owns pricing, tax, payment attempts, webhook state and order creation.

  7. Confirmation and status — wait for the checkout's authoritative CONFIRMED state, then use its orderReference with the appropriate credential below. A browser payment-success callback cannot create or confirm an order.

  8. Legacy compatibility — /guides/orders. POST /public/orders remains a deprecated, unpaid PENDING quote-intake path with line-item-only totals, not a replacement for checkout sessions. GET /public/orders/:id and /public/orders/number/:num?email=… remain deprecated compatibility lookups; do not make them the default confirmation or account flow for a new integration. Their existing support and deprecation policy remain in force.

  9. Returns — /guides/returns and customer accounts. New signed-in flows use authenticated order-by-reference return routes. The documented legacy email-match flow remains available for compatibility.

Confirmation, status and account credentials

orderReference is a stable locator, not an access credential. Preserve it exactly as returned; never turn it into a database id or display order number. The three post-purchase surfaces use different authority:

SurfaceAPI readRequired context and credential
Immediate checkout confirmationGET /public/orders/by-reference/:orderReference/confirmationx-organization-slug plus the live x-checkout-session-token bound to that exact final order
Emailed order statusGET /public/orders/token/:tokenx-organization-slug plus the separately emailed o-family token in the path; no customer sign-in
Signed-in order detailGET /customer/account/orders/by-reference/:orderReferencex-organization-slug plus the owning customer's session

Immediate confirmation is a private, non-cacheable read and does not expose or recover the emailed status credential. The checkout token and the o token are not interchangeable, and a hosted account cookie does not automatically authenticate a separate BYO origin. See order status links and customer accounts for those separate flows.

The two reference storefronts (EKGIS Naturals + Idaho Pro Gear) consume exactly this surface — they're the working proof of the BYO-frontend model.

Analytics choice at hosted handoff

Before enabling hosted analytics for an opt-in storefront, follow the consent integration contract and onboarding checklist. The handoff/update API, adoption controls, hosted consent enforcement and event handling have landed; adoption and consent-based analytics remain unavailable for live use. A successful API handoff does not enable them. Keep your own storefront tags, CMP, choice capture and privacy disclosures under your control, and link the tenant privacy policy and hosted /cookies notice at the public handoff.