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 model | Intended caller | Authentication | Base URL |
|---|---|---|---|
| Storefront App Proxy | Theme JavaScript and the JavaScript SDK | Shopify 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:
| Method | Path | Purpose |
|---|---|---|
POST | /apps/tab-cross-sell/recommendations/presence | PDP-only advisory loading hint |
POST | /apps/tab-cross-sell/recommendations/rendered | Market-aware authoritative HTML |
POST | /apps/tab-cross-sell/recommendations/resolve | UI-agnostic recommendation facts |
POST | /apps/tab-cross-sell/events | Impression, 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:
| Method | Path under the proxy base | Purpose |
|---|---|---|
POST | /bundle-offers/resolve | Resolve by slot key or explicit offer IDs |
POST | /bundle-offers/quote | Re-quote the complete exact variant selection |
POST | /bundle-offers/pdp/rendered | Render 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.