Ads & Recommendations
Surface personalized product recommendations and sponsored placements in your storefront.
What you'll learn
- The difference between organic and sponsored content
- How to fetch organic recommendations (user top picks, cart toppers)
- How to fetch sponsored product listings that blend paid placements into search results
- How to fetch sponsored recommendations (sponsored top picks, sponsored cart toppers)
- How to request frequently bought together products
- The ad client data payload structure required by sponsored endpoints
- How to distinguish organic vs sponsored products in the response
Prerequisites
- A Store UUID (staging:
e87437f2-3e35-4738-af5e-6307e368255c) - The store must have product recommendations enabled in its settings
- For sponsored endpoints, the store needs a Surfside integration configured with
account_idandsite_id - cURL or any HTTP client
Organic vs Sponsored Content
The API provides two flavors of product recommendations:
Organic — Pure recommendations based on user behavior, popularity, and store configuration. Fetched via GET requests with no additional payload.
Sponsored — Recommendations that blend paid ad placements from the Surfside ad platform alongside organic results. Fetched via POST requests that include a client_data payload with browser, location, and session information.
The frontend automatically upgrades organic calls to sponsored when the store has the relevant feature toggle enabled (e.g., sponsoredProductsEnabled or productPlacementEnabled). As a consumer, you should always prefer the sponsored endpoint when ad integrations are active, falling back to the organic endpoint when they're not.
Organic Recommendations
User Top Picks
Personalized product suggestions for the current user. If no user is authenticated, returns store-level popular picks.
Endpoint: GET /api/v1/products/recommendations/user-top-picks
Auth: jwt_optional_authenticated (works with or without a JWT)
Query Parameters
| Parameter | Type | Description |
|---|---|---|
limit |
integer | Max products to return (default: 5, max: 50) |
brand |
string | Filter by brand slug |
category |
string | Filter by category slug |
type |
string | Filter by product type |
tag |
string | Filter by tag |
delivery_type |
string | One of: all, pickup, express, scheduled_delivery, kiosk |
excludes |
string | Product IDs to exclude |
cart_total |
string | Current cart total (for relevance tuning) |
max_price |
string | Max price filter |
min_price |
string | Min price filter |
cURL
curl -X GET "https://ecom-api.staging.blaze.me/api/v1/products/recommendations/user-top-picks?limit=5" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"
JavaScript (fetch)
const STORE_UUID = "e87437f2-3e35-4738-af5e-6307e368255c";
const BASE_URL = "https://ecom-api.staging.blaze.me";
const response = await fetch(
`${BASE_URL}/api/v1/products/recommendations/user-top-picks?limit=5`,
{
headers: {
"Content-Type": "application/vnd.api+json",
Accept: "application/vnd.api+json",
"X-Store": STORE_UUID,
},
},
);
const { data, meta } = await response.json();
console.log(`Got ${data.length} top picks`);
Cart Toppers
Products commonly added alongside items already in the cart. Same interface as user top picks.
Endpoint: GET /api/v1/products/recommendations/cart-toppers
Auth: jwt_optional_authenticated
Accepts the same query parameters as user top picks.
Legacy Recommended
Endpoint: GET /api/v1/products/recommended
Auth: jwt_optional_authenticated
Note: This is a legacy endpoint. Prefer
user-top-picksandcart-toppersfor new integrations.
Sponsored Product Listing
When ad integrations are active, the product listing endpoint switches from GET /api/v1/products to POST /api/v1/products/sponsored. This blends sponsored placements into the standard product grid.
Endpoint: POST /api/v1/products/sponsored
Auth: jwt_optional_authenticated
The sponsored endpoint accepts all the same query parameters as the regular product listing (passed as URL query params), plus a JSON body containing the ad client data payload.
Sponsored products are interleaved with organic results — the response mixes them together so the product grid appears natural.
cURL
curl -X POST "https://ecom-api.staging.blaze.me/api/v1/products/sponsored?limit=20" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c" \
-d '{
"data": {
"type": "recommendations",
"attributes": {
"url": "https://my-store.blaze.me/products",
"screen": { "height": 1080, "width": 1920 },
"navigator": {
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"language": "en-US"
},
"mobile": false,
"account_id": "your-surfside-account-id",
"site_id": "your-surfside-site-id",
"channel_id": "your-channel-id",
"channel_type": "WEB",
"zone_id": "zone-for-current-page",
"client_ip": "203.0.113.42",
"surfside_domain_id": "surf-domain-id-from-cookie",
"session_id": "current-session-id",
"store_location_data": {
"coords": { "latitude": 34.0522, "longitude": -118.2437 },
"zip": "90001",
"city": "Los Angeles",
"country": "US",
"state": "CA"
},
"location_data": {
"zip": "90210",
"country": "US",
"city": "Beverly Hills",
"region": "CA",
"utc_offset": -7,
"timezone": "America/Los_Angeles",
"coords": {
"latitude": 34.0901,
"longitude": -118.4065,
"accuracy": 20
}
}
}
}
}'
JavaScript (fetch)
const STORE_UUID = "e87437f2-3e35-4738-af5e-6307e368255c";
const BASE_URL = "https://ecom-api.staging.blaze.me";
const clientDataPayload = {
data: {
type: "recommendations",
attributes: {
url: window.location.href,
screen: {
height: window.screen.height,
width: window.screen.width,
},
navigator: {
user_agent: window.navigator.userAgent,
language: window.navigator.language,
},
mobile: /Android|iPhone|iPad/i.test(navigator.userAgent),
account_id: "your-surfside-account-id",
site_id: "your-surfside-site-id",
channel_id: "your-channel-id",
channel_type: "WEB",
zone_id: "zone-for-current-page",
client_ip: "203.0.113.42",
surfside_domain_id: null,
session_id: "current-session-id",
store_location_data: {
coords: { latitude: 34.0522, longitude: -118.2437 },
zip: "90001",
city: "Los Angeles",
country: "US",
state: "CA",
},
location_data: {
zip: "90210",
country: "US",
city: "Beverly Hills",
region: "CA",
utc_offset: -7,
timezone: "America/Los_Angeles",
coords: { latitude: 34.0901, longitude: -118.4065, accuracy: 20 },
},
},
},
};
const response = await fetch(`${BASE_URL}/api/v1/products/sponsored?limit=20`, {
method: "POST",
headers: {
"Content-Type": "application/vnd.api+json",
Accept: "application/vnd.api+json",
"X-Store": STORE_UUID,
},
body: JSON.stringify(clientDataPayload),
});
const { data, meta } = await response.json();
// Sponsored products have is_promoted: true and a product_placement_campaign_id
data.forEach((p) => {
if (p.attributes.is_promoted) {
console.log(`[SPONSORED] ${p.attributes.name}`);
} else {
console.log(`${p.attributes.name}`);
}
});
Sponsored Recommendations
When ad integrations are active, the frontend upgrades organic recommendation calls to their sponsored counterparts. Sponsored recommendations use POST and include the same client data payload.
Sponsored User Top Picks
Endpoint: POST /api/v1/products/recommendations/sponsored-user-top-picks
Auth: jwt_optional_authenticated
Same query parameters as organic user top picks, plus the client data body.
Sponsored Cart Toppers
Endpoint: POST /api/v1/products/recommendations/sponsored-cart-toppers
Auth: jwt_optional_authenticated
Same query parameters as organic cart toppers, plus the client data body.
How the Frontend Decides
The frontend uses feature toggles to decide whether to call the organic or sponsored variant:
- Check if the store has
productPlacementEnabled(for recommendations) orsponsoredProductsEnabled(for product listing) - If enabled, gather client data (screen, navigator, location, Surfside cookie) via the
https://g.surfside.io/enrichendpoint - Call the sponsored
POSTendpoint with the client data payload - If disabled or client data fails to load, fall back to the organic
GETendpoint
Frequently Bought Together
Returns products commonly purchased alongside a specific product. This endpoint always uses POST with the client data payload (the ad platform determines both organic and sponsored recommendations).
Endpoint: POST /api/v1/products/recommendations/frequently-bought-together/{product_id}
Auth: jwt_optional_authenticated
The product_id can be the product's numeric ID or slug.
cURL
curl -X POST "https://ecom-api.staging.blaze.me/api/v1/products/recommendations/frequently-bought-together/12345?limit=5" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c" \
-d '{
"data": {
"type": "recommendations",
"attributes": {
"url": "https://my-store.blaze.me/products/12345",
"screen": { "height": 1080, "width": 1920 },
"navigator": {
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"language": "en-US"
},
"mobile": false,
"account_id": "your-surfside-account-id",
"site_id": "your-surfside-site-id",
"channel_id": "your-channel-id",
"channel_type": "WEB",
"zone_id": "zone-for-product-page",
"client_ip": "203.0.113.42",
"surfside_domain_id": null,
"session_id": "current-session-id",
"store_location_data": {
"coords": { "latitude": 34.0522, "longitude": -118.2437 },
"zip": "90001",
"city": "Los Angeles",
"country": "US",
"state": "CA"
},
"location_data": {
"zip": "90210",
"country": "US",
"city": "Beverly Hills",
"region": "CA",
"utc_offset": -7,
"timezone": "America/Los_Angeles",
"coords": { "latitude": 34.0901, "longitude": -118.4065, "accuracy": 20 }
}
}
}
}'
JavaScript (fetch)
const STORE_UUID = "e87437f2-3e35-4738-af5e-6307e368255c";
const BASE_URL = "https://ecom-api.staging.blaze.me";
const productId = "12345";
const response = await fetch(
`${BASE_URL}/api/v1/products/recommendations/frequently-bought-together/${productId}?limit=5`,
{
method: "POST",
headers: {
"Content-Type": "application/vnd.api+json",
Accept: "application/vnd.api+json",
"X-Store": STORE_UUID,
},
body: JSON.stringify(clientDataPayload), // same payload structure as above
},
);
const { data, meta } = await response.json();
console.log(`Recommendation ID: ${meta.recommendation_id}`);
data.forEach((p) => console.log(p.attributes.name));
Response
The response includes a recommendation_id in the meta, which can be used for tracking/attribution:
{
"meta": {
"recommendation_id": "rec-abc-123"
},
"data": [
{
"id": "67890",
"type": "products",
"attributes": {
"name": "Rolling Papers",
"is_promoted": true,
"product_placement_campaign_id": "campaign-xyz",
"extras": { ... }
}
},
{
"id": "67891",
"type": "products",
"attributes": {
"name": "Grinder",
"is_promoted": false,
"product_placement_campaign_id": null
}
}
]
}
Ad Client Data Payload
All sponsored endpoints require a JSON body with the following structure:
{
"data": {
"type": "recommendations",
"attributes": {
"url": "string — current page URL",
"screen": {
"width": "integer — screen width in pixels",
"height": "integer — screen height in pixels"
},
"navigator": {
"user_agent": "string — browser user agent",
"language": "string — browser language (e.g., 'en-US')"
},
"mobile": "boolean — whether the client is a mobile device",
"account_id": "string — Surfside account ID from store integration",
"site_id": "string — Surfside site ID from store integration",
"channel_id": "string — Surfside channel ID",
"channel_type": "string — 'WEB' or 'KIOSK'",
"zone_id": "string — Surfside ad zone/placement ID for the current page",
"client_ip": "string — client IP address",
"surfside_domain_id": "string|null — Surfside domain cookie value",
"session_id": "string|null — current browsing session ID",
"store_location_data": {
"coords": {
"latitude": "float|null",
"longitude": "float|null"
},
"zip": "string|null",
"city": "string|null",
"country": "string|null",
"state": "string|null"
},
"location_data": {
"zip": "string|null",
"country": "string|null",
"city": "string|null",
"region": "string|null",
"utc_offset": "integer|null",
"timezone": "string|null",
"coords": {
"latitude": "float|null",
"longitude": "float|null",
"accuracy": "integer|null"
}
}
}
}
}
Where to get the values
screen,navigator,mobile— From the browser'swindow.screenandwindow.navigatorAPIsaccount_id,site_id— From the store's Surfside site integration (GET /api/v1/store/site/integrations/surfside, keyskey_1andkey_2)channel_id— A constant for your Surfside channelzone_id— The Surfside ad placement ID mapped to the current page/routeclient_ip— Fetched from the Surfside enrich endpoint (https://g.surfside.io/enrich)location_data— Also returned by the Surfside enrich endpointstore_location_data— From the store's address and coordinates (returned byGET /api/v1/store)surfside_domain_id— A cookie set by the Surfside tracking pixelsession_id— Your application's session identifier
Distinguishing Organic vs Sponsored Results
In the response, every product includes two fields that identify sponsored placements:
is_promoted(boolean) —trueif the product is a paid/sponsored placement,falseornullfor organic resultsproduct_placement_campaign_id(string|null) — The ad campaign ID that sponsored this placement.nullfor organic resultsextras(object|null) — Additional ad metadata from the placement platform.nullfor organic results
Use these fields to:
- Render a "Sponsored" badge on promoted products
- Track ad impressions and clicks for attribution
- Report conversions back to the ad platform
function isSponsored(product) {
return product.attributes.is_promoted === true;
}
// Render products with sponsorship indicator
data.forEach((product) => {
const label = isSponsored(product) ? "[Sponsored] " : "";
console.log(`${label}${product.attributes.name}`);
});
Endpoint Summary
| Endpoint | Method | Purpose |
|---|---|---|
/api/v1/products/recommendations/user-top-picks |
GET | Organic personalized picks |
/api/v1/products/recommendations/cart-toppers |
GET | Organic cart add-on suggestions |
/api/v1/products/sponsored |
POST | Sponsored product listing (replaces GET /products when ads are active) |
/api/v1/products/recommendations/sponsored-user-top-picks |
POST | Sponsored personalized picks |
/api/v1/products/recommendations/sponsored-cart-toppers |
POST | Sponsored cart add-on suggestions |
/api/v1/products/recommendations/frequently-bought-together/{product_id} |
POST | Products commonly bought together |
/api/v1/products/recommended |
GET | Legacy recommended (deprecated) |
What's Next?
- Product listing: See the Quick Start guide for basic product queries
- Authentication: See the Authentication guide for JWT flows
- General concepts: See the General Concepts guide for headers, errors, and JSON
format