Tab Commerce

Storefront API and authentication

Call the Shopify App Proxy safely and understand the browser-facing endpoint contract.

Tab Upsell & Bundles browser integrations use Shopify App Proxy authentication. There is no merchant-managed API credential or browser token.

Access modelIntended callerAuthenticationBase URL
Storefront App ProxyTheme JavaScript and the JavaScript SDKShopify signs the proxied request/apps/tab-cross-sell on the shop domain

Storefront App Proxy

All browser examples in these docs call the storefront’s relative App Proxy path:

const response = await fetch('/apps/tab-cross-sell/recommendations/resolve', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    placement: 'pdp',
    source_products: [
      { product_id: 'gid://shopify/Product/1234567890' },
    ],
  }),
});

Do not call api.cross-sell.tabcommerce.io from theme code. The shop-relative route is important: Shopify signs the proxied request, and the app verifies that signature before handling it. There is no browser-managed bearer token and no signature-generation code to copy into the theme.

The examples use /apps/tab-cross-sell; substitute the installed shop’s configured same-origin proxy path. Custom API/SDK access requires the shop’s stored API entitlement, not a credential pasted into browser code.

The Upsell paths are:

MethodPathPurpose
POST/apps/tab-cross-sell/recommendations/presencePDP-only advisory loading hint
POST/apps/tab-cross-sell/recommendations/renderedMarket-aware authoritative HTML
POST/apps/tab-cross-sell/recommendations/resolveUI-agnostic recommendation facts
POST/apps/tab-cross-sell/eventsImpression, click, and add-to-cart batches

Responses use a { "data": ... } envelope. Recommendation endpoints can return HTTP 200 with empty candidates or empty HTML when there is nothing safe to render. Treat that as a no-op, not as an error UI.

Bundle endpoints

Bundles have separate request shapes and event identities:

MethodPath under the proxy basePurpose
POST/bundle-offers/resolveResolve by slot key or explicit offer IDs
POST/bundle-offers/quoteRe-quote the complete exact variant selection
POST/bundle-offers/pdp/renderedRender an eligible member-product offer

The PDP rendered route, resolve by slot_key, and the quotes they issue use the bundle storefront entitlement that every plan includes. Resolve by explicit offer_ids, and its quotes, require API access. Bundle quotes carry a short expiry and must be refreshed after a selection change. Use the Bundle SDK for cart confirmation and event delivery rather than sending Upsell candidate events for a bundle.

Browser security rules

  • Keep every browser request shop-relative under /apps/tab-cross-sell.
  • Never construct or forward Shopify App Proxy signatures yourself.
  • Never add a private bearer token to storefront code; none is required or accepted by these endpoints.
  • Do not proxy customer-supplied shop or customer IDs from an unsigned endpoint. Signed App Proxy context remains authoritative.

Request context

/recommendations/rendered requires the current Market country and presentment currency. Missing, invalid, or mismatched Market context returns HTTP 200 with empty HTML rather than showing a price in the wrong currency.

{
  "placement": "pdp",
  "source_products": [
    { "product_id": "gid://shopify/Product/1234567890" }
  ],
  "cart": {
    "currency": "USD",
    "lines": []
  },
  "context": {
    "country": "US",
    "language": "en"
  },
  "render": {
    "layout": "carousel",
    "heading": "Complete your order",
    "add_to_cart_label": "Add"
  }
}

Customer identity in a request body is only a client hint. The server trusts a signed Shopify logged_in_customer_id, not a browser-provided customer ID.

Limits and recovery

  • Event batches contain 1–100 items.
  • Unsupported placements return an empty recommendation result.
  • Invalid event items appear in data.rejected; valid items in the same batch can still be accepted.
  • A rate-limited event request can return 429. Event delivery must remain best-effort and must never block cart or checkout actions.
  • A malformed or unsigned App Proxy request returns 401. Fix the request path instead of adding a client-side credential fallback.