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.
| Mechanism | Estimable today (display-only) | Binding at POST /public/orders | Bound by checkout sessions (M3) |
|---|---|---|---|
| Line items + subtotal | cart state | Yes — items[], subtotalInCents | — |
| Order total | subtotal (± your own estimates) | Yes — totalInCents == subtotalInCents | total incl. provider-gated tax, discounts, and selected shipping |
| Shipping | zone configuration from GET /public/shipping/zones; current options from checkout-session reprice | No — no shipping line persisted | Yes, for carts requiring shipping — bind locks the selected option's shippingInCents into the total |
| Coupons | POST /public/cart/apply-coupon (pure preview) | No — not attached | usage counted at placement |
| Auto-discounts | POST /public/auto-discounts/calculate | No — not attached | applied at checkout |
| Bundles | check-bundles / items/:slug/bundles | No — not attached | applied at checkout |
| Tax | cart display estimate only; hosted tax preview exists on staging, with BYO adoption pending acceptance/release | No — taxInCents is 0 on legacy orders | bind 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
subtotalInCentsor label the extras "estimated, finalized at checkout." For M3 checkout sessions whose carts require shipping, select from reprice'savailableShippingOptionsand read the tax- and shipping-inclusive total afterbind. Treat any amount returned byreprice, includingtaxInCents=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:
-
Catalog —
/guides/catalog.GET /public/itemsand/public/items/:slugfor product + variant data (public reads returnACTIVEonly). -
Collections & pages —
/guides/collections-pages.GET /public/collections,/public/collections/:slug/items, and/public/pagesfor merchandising + CMS content. -
Inventory —
/guides/inventory.GET /public/inventory/:itemIdfor 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), andavailableQuantityis the units sellable now. For aSALEitem with variants, the line must name one exact livevariantId; an item-level target is refused, including when only archived variant history remains. Omission ornulltargets 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 itsavailableQuantity— 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/sessionschecks server-repricedSALEquantities before persisting a session, including expanded components of composed products. A measured shortfall returns HTTP 409 withcode: "RESOURCE_CONFLICT",details.reason: "INSUFFICIENT_INVENTORY", anddetails.lines[]naming every short stock target. No session is created on that refusal; correct the quantity in the storefront and submit create again.bindrechecks 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
SALEstock 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.RENTALandSERVICElines are outside this guard entirely — rental capacity comes from the booking/availability path, and services have no stock.POST /public/availability/checkremains the booking/rental date-range preflight (it requiresstartDate/endDate, matchesRENTALitems only, and reports everySALEline as unavailable), so do not wire it into a retail cart. -
Pricing —
/guides/pricing. The cart-math previews:POST /public/cart/apply-coupon,POST /public/auto-discounts/calculate,POST /public/cart/check-bundles, and the proactiveGET /public/items/:slug/bundles. All display-only (see the boundary above). -
Shipping —
/guides/shipping. UseGET /public/shipping/zonesonly for zone configuration and live-rate eligibility. For a cart with shippable lines on the M3 checkout-session path, create a session, callrepricewith the complete address, render its fullavailableShippingOptionsset, and send the selected option'srateIdasshippingRateIdon the next reprice and onbind. Bind locks that shipping charge before payment or confirmation. -
Checkout —
/guides/byo-checkout. Use checkout sessions for the server-authoritative total and the cartonium Stripe Payment Element handoff. Create/read/reprice/bind calls usex-organization-slug; thepayment-sessionhandoff 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. -
Confirmation and status — wait for the checkout's authoritative
CONFIRMEDstate, then use itsorderReferencewith the appropriate credential below. A browser payment-success callback cannot create or confirm an order. -
Legacy compatibility —
/guides/orders.POST /public/ordersremains a deprecated, unpaidPENDINGquote-intake path with line-item-only totals, not a replacement for checkout sessions.GET /public/orders/:idand/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. -
Returns —
/guides/returnsand 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:
| Surface | API read | Required context and credential |
|---|---|---|
| Immediate checkout confirmation | GET /public/orders/by-reference/:orderReference/confirmation | x-organization-slug plus the live x-checkout-session-token bound to that exact final order |
| Emailed order status | GET /public/orders/token/:token | x-organization-slug plus the separately emailed o-family token in the path; no customer sign-in |
| Signed-in order detail | GET /customer/account/orders/by-reference/:orderReference | x-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.
Related
- Tenants & context — the
x-organization-slugheader - Orders & fulfillment — the full order contract + the legacy order persistence callout
- BYO checkout API — the checkout-session and payment handoff sequence for custom storefronts
- Error envelope — the shape every failed call returns
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.