Recommend API
The Recommend API provides personalized product recommendations based on user behavior, browsing history, and purchase patterns.
Use this API to build custom recommendation widgets, carousels, or integrate recommendations into your existing UI - without relying on edrone's built-in frames.
Overview
POST https://api.edrone.me/recommend
Content-Type: application/json
This API uses camelCase for JSON body parameters (e.g., appId, productInteractions), unlike the Trace API which uses snake_case (e.g., app_id, product_ids). This is because the Recommend API accepts JSON, while Trace API uses form-urlencoded data.
The API returns personalized product recommendations using multiple algorithms:
- Association Rules (ARULES) - "Customers also bought"
- Semantic Similarity (SBERT) - Similar products based on descriptions
- Collaborative Filtering - Based on user behavior patterns
- Checkout recommendations - Complementary products for cart
Request format
Send a JSON body with the recommendation context:
{
"appId": "YOUR_APP_ID",
"productInteractions": [
{
"productInteractionType": "product_view",
"productId": "SKU123",
"productSku": "SKU123",
"utcInteractionDate": "2024-01-15T10:30:00.000Z"
},
{
"productInteractionType": "add_to_cart",
"productId": "SKU456",
"productSku": "SKU456",
"utcInteractionDate": "2024-01-15T10:32:00.000Z"
}
],
"recommendFrameInteractions": [],
"currentProductIds": "SKU789",
"currentProductSkus": "SKU789",
"currentCategoryIds": "cat-shoes",
"cId": "unique-client-id",
"fpccid": "fingerprint-cookie-id"
}
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | Your edrone App ID |
productInteractions | array | Yes | History of user's product interactions |
recommendFrameInteractions | array | No | History of recommendation frame interactions |
currentProductIds | string | No | Currently viewed product ID(s) |
currentProductSkus | string | No | Currently viewed product SKU(s) |
currentCategoryIds | string | No | Currently viewed category ID(s) |
cId | string | No | Client ID from localStorage |
fpccid | string | No | Fingerprint cookie ID |
marketingMachineFrameConfigurationId | string | No | Specific frame configuration to use |
filterByGroups | string | No | Filter recommendations by product groups |
externalCookieGaId | string | No | Google Analytics cookie for enhanced tracking |
Product interaction types
| Type | Description |
|---|---|
product_view | User viewed a product page |
add_to_cart | User added product to cart |
order | User purchased the product |
Response format
{
"version": "2024011512",
"trackerProduct": {
"productId": "SKU789",
"title": "Running Shoes Pro",
"imageUrl": "https://example.com/images/sku789.jpg",
"url": "https://example.com/products/sku789",
"price": "129.99",
"categoryId": "cat-shoes"
},
"recommended": [
{
"productId": "SKU101",
"title": "Sport Socks 3-Pack",
"imageUrl": "https://example.com/images/sku101.jpg",
"url": "https://example.com/products/sku101",
"price": "19.99",
"categoryId": "cat-accessories"
},
{
"productId": "SKU102",
"title": "Running Shorts",
"imageUrl": "https://example.com/images/sku102.jpg",
"url": "https://example.com/products/sku102",
"price": "49.99",
"categoryId": "cat-clothing"
}
],
"alternative": [
{
"productId": "SKU201",
"title": "Running Shoes Lite",
"imageUrl": "https://example.com/images/sku201.jpg",
"url": "https://example.com/products/sku201",
"price": "99.99",
"categoryId": "cat-shoes"
}
]
}
Response fields
| Field | Type | Description |
|---|---|---|
version | string | Current recommendation engine version (for cache invalidation) |
trackerProduct | object | The product being viewed (context) |
recommended | array | Complementary products ("You may also like") |
alternative | array | Similar/alternative products ("Similar items") |
Implementation examples
- JavaScript
- React
- Python
- PHP
class RecommendationService {
constructor(appId) {
this.appId = appId;
this.endpoint = 'https://api.edrone.me/recommend';
this.storageKey = 'recommended_data';
}
// Get current recommendation state from localStorage
getState() {
const stored = localStorage.getItem(this.storageKey);
if (stored) {
return JSON.parse(stored);
}
return {
appId: this.appId,
productInteractions: [],
recommendFrameInteractions: [],
version: null
};
}
// Save state to localStorage
saveState(state) {
state.appId = this.appId;
localStorage.setItem(this.storageKey, JSON.stringify(state));
}
// Track product interaction
trackInteraction(type, productId, productSku = null) {
const state = this.getState();
// Keep last 100 product_view and add_to_cart interactions
const filtered = state.productInteractions.filter(i => {
if (i.productInteractionType === 'product_view') return true;
if (i.productInteractionType === 'add_to_cart') return true;
if (i.productInteractionType === 'order') return true;
return false;
}).slice(-200);
state.productInteractions = filtered;
state.productInteractions.push({
productInteractionType: type,
productId: productId,
productSku: productSku || productId,
utcInteractionDate: new Date().toISOString()
});
this.saveState(state);
}
// Fetch recommendations
async getRecommendations(currentProductId, currentCategoryId = null) {
const state = this.getState();
const requestBody = {
...state,
currentProductIds: currentProductId,
currentProductSkus: currentProductId,
currentCategoryIds: currentCategoryId
};
const response = await fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody),
credentials: 'include'
});
if (!response.ok) {
throw new Error('Failed to fetch recommendations');
}
const data = await response.json();
// Update version if changed
if (data.version && data.version !== state.version) {
state.version = data.version;
state.recommendFrameInteractions = []; // Clear on version change
this.saveState(state);
}
return data;
}
}
// Usage
const recommendations = new RecommendationService('YOUR_APP_ID');
// Track when user views a product
recommendations.trackInteraction('product_view', 'SKU123');
// Track when user adds to cart
recommendations.trackInteraction('add_to_cart', 'SKU456');
// Get recommendations for current product
const result = await recommendations.getRecommendations('SKU789', 'cat-shoes');
console.log('Recommended products:', result.recommended);
console.log('Alternative products:', result.alternative);
import { useState, useEffect, useCallback } from 'react';
const STORAGE_KEY = 'recommended_data';
const ENDPOINT = 'https://api.edrone.me/recommend';
export function useRecommendations(appId) {
const [recommendations, setRecommendations] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const getState = useCallback(() => {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
return JSON.parse(stored);
}
return {
appId,
productInteractions: [],
recommendFrameInteractions: [],
version: null
};
}, [appId]);
const saveState = useCallback((state) => {
state.appId = appId;
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
}, [appId]);
const trackInteraction = useCallback((type, productId, productSku = null) => {
const state = getState();
state.productInteractions.push({
productInteractionType: type,
productId,
productSku: productSku || productId,
utcInteractionDate: new Date().toISOString()
});
// Keep last 200 interactions
state.productInteractions = state.productInteractions.slice(-200);
saveState(state);
}, [getState, saveState]);
const fetchRecommendations = useCallback(async (productId, categoryId = null) => {
setLoading(true);
setError(null);
try {
const state = getState();
const response = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...state,
currentProductIds: productId,
currentProductSkus: productId,
currentCategoryIds: categoryId
}),
credentials: 'include'
});
if (!response.ok) throw new Error('Failed to fetch');
const data = await response.json();
// Handle version change
if (data.version && data.version !== state.version) {
state.version = data.version;
state.recommendFrameInteractions = [];
saveState(state);
}
setRecommendations(data);
return data;
} catch (err) {
setError(err.message);
return null;
} finally {
setLoading(false);
}
}, [getState, saveState]);
return {
recommendations,
loading,
error,
trackInteraction,
fetchRecommendations
};
}
// Usage in component
function ProductPage({ product }) {
const {
recommendations,
loading,
trackInteraction,
fetchRecommendations
} = useRecommendations('YOUR_APP_ID');
useEffect(() => {
trackInteraction('product_view', product.id);
fetchRecommendations(product.id, product.categoryId);
}, [product.id]);
if (loading) return <div>Loading recommendations...</div>;
return (
<div>
<h2>You may also like</h2>
<div className="recommendations-grid">
{recommendations?.recommended?.map(item => (
<ProductCard key={item.productId} product={item} />
))}
</div>
<h2>Similar products</h2>
<div className="alternatives-grid">
{recommendations?.alternative?.map(item => (
<ProductCard key={item.productId} product={item} />
))}
</div>
</div>
);
}
import requests
import json
from datetime import datetime, timezone
class EdroneRecommendations:
def __init__(self, app_id: str):
self.app_id = app_id
self.endpoint = "https://api.edrone.me/recommend"
def get_recommendations(
self,
product_interactions: list,
current_product_id: str = None,
current_category_id: str = None,
client_id: str = None
) -> dict:
"""
Fetch product recommendations from edrone.
Args:
product_interactions: List of user's product interactions
current_product_id: Currently viewed product ID
current_category_id: Currently viewed category ID
client_id: Unique client identifier
Returns:
Dictionary with recommended and alternative products
"""
payload = {
"appId": self.app_id,
"productInteractions": product_interactions,
"recommendFrameInteractions": [],
"currentProductIds": current_product_id,
"currentProductSkus": current_product_id,
"currentCategoryIds": current_category_id,
}
if client_id:
payload["cId"] = client_id
response = requests.post(
self.endpoint,
json=payload,
headers={"Content-Type": "application/json"},
timeout=10
)
response.raise_for_status()
return response.json()
@staticmethod
def create_interaction(
interaction_type: str,
product_id: str,
product_sku: str = None
) -> dict:
"""Create a product interaction object."""
return {
"productInteractionType": interaction_type,
"productId": product_id,
"productSku": product_sku or product_id,
"utcInteractionDate": datetime.now(timezone.utc).isoformat()
}
# Usage
edrone = EdroneRecommendations("YOUR_APP_ID")
# Build interaction history
interactions = [
edrone.create_interaction("product_view", "SKU123"),
edrone.create_interaction("product_view", "SKU456"),
edrone.create_interaction("add_to_cart", "SKU123"),
]
# Get recommendations
result = edrone.get_recommendations(
product_interactions=interactions,
current_product_id="SKU789",
current_category_id="shoes"
)
print("Recommended:", [p["title"] for p in result.get("recommended", [])])
print("Alternatives:", [p["title"] for p in result.get("alternative", [])])
<?php
class EdroneRecommendations {
private string $appId;
private string $endpoint = 'https://api.edrone.me/recommend';
public function __construct(string $appId) {
$this->appId = $appId;
}
public function getRecommendations(
array $productInteractions,
?string $currentProductId = null,
?string $currentCategoryId = null,
?string $clientId = null
): array {
$payload = [
'appId' => $this->appId,
'productInteractions' => $productInteractions,
'recommendFrameInteractions' => [],
'currentProductIds' => $currentProductId,
'currentProductSkus' => $currentProductId,
'currentCategoryIds' => $currentCategoryId,
];
if ($clientId) {
$payload['cId'] = $clientId;
}
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $this->endpoint,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json'
],
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("Recommendation API error: HTTP $httpCode");
}
return json_decode($response, true);
}
public static function createInteraction(
string $type,
string $productId,
?string $productSku = null
): array {
return [
'productInteractionType' => $type,
'productId' => $productId,
'productSku' => $productSku ?? $productId,
'utcInteractionDate' => gmdate('Y-m-d\TH:i:s\Z')
];
}
}
// Usage
$edrone = new EdroneRecommendations('YOUR_APP_ID');
$interactions = [
EdroneRecommendations::createInteraction('product_view', 'SKU123'),
EdroneRecommendations::createInteraction('add_to_cart', 'SKU456'),
];
$result = $edrone->getRecommendations(
$interactions,
'SKU789',
'shoes'
);
foreach ($result['recommended'] as $product) {
echo "Recommended: {$product['title']} - {$product['price']}\n";
}
localStorage integration
When implementing on the frontend, maintain recommendation state in localStorage for:
- Personalization continuity across page loads
- Interaction history for better recommendations
- Version tracking for cache invalidation
Storage structure
// Key: "recommended_data"
{
"appId": "YOUR_APP_ID",
"productInteractions": [
{
"productInteractionType": "product_view",
"productId": "SKU123",
"productSku": "SKU123",
"utcInteractionDate": "2024-01-15T10:30:00.000Z"
}
],
"recommendFrameInteractions": [
{
"trackerProductGroupId": "frame-123",
"utcInteractionDate": "2024-01-15T10:31:00.000Z"
}
],
"version": "2024011512"
}
Version handling
The API returns a version field. When this changes:
- Clear
recommendFrameInteractionsarray - Update stored version
- This ensures users see fresh recommendations when the recommendation engine is updated
// After receiving response
if (response.version !== storedState.version) {
storedState.version = response.version;
storedState.recommendFrameInteractions = [];
localStorage.setItem('recommended_data', JSON.stringify(storedState));
}
Best practices
1. Track meaningful interactions
// Good - track actual user intent
trackInteraction('product_view', productId); // User spent time on product page
trackInteraction('add_to_cart', productId); // User showed purchase intent
// Avoid - don't track everything
// Don't track quick scrolls, accidental clicks, or list views
2. Limit interaction history
Keep the last 100-200 interactions per type to balance personalization vs. request size:
const MAX_INTERACTIONS = 100;
state.productInteractions = state.productInteractions
.filter(i => i.productInteractionType === 'product_view')
.slice(-MAX_INTERACTIONS);
3. Handle empty responses gracefully
if (!recommendations?.recommended?.length) {
// Show fallback content: bestsellers, new arrivals, etc.
return <FallbackRecommendations />;
}
4. Cache responses appropriately
Recommendations can be cached for short periods (1-5 minutes) to reduce API calls:
const CACHE_TTL = 60000; // 1 minute
const cacheKey = `recommendations_${productId}`;
const cached = sessionStorage.getItem(cacheKey);
if (cached) {
const { data, timestamp } = JSON.parse(cached);
if (Date.now() - timestamp < CACHE_TTL) {
return data;
}
}
const fresh = await fetchRecommendations(productId);
sessionStorage.setItem(cacheKey, JSON.stringify({
data: fresh,
timestamp: Date.now()
}));
Error handling
HTTP status codes
| Status | Meaning |
|---|---|
200 | Success - recommendations returned |
400 | Bad request - invalid JSON or missing required fields |
500 | Server error - temporary issue, retry later |
Error response format
When an error occurs, the API returns:
{
"error": "Service temporarily unavailable"
}
Or for internal errors:
{
"error": "Internal Server Error"
}
Handling errors in code
try {
const response = await fetch('https://api.edrone.me/recommend', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody)
});
if (!response.ok) {
// Handle HTTP errors
console.error('Recommend API error:', response.status);
return showFallbackProducts(); // Show bestsellers, etc.
}
const data = await response.json();
if (data.error) {
// Handle API-level errors
console.error('API error:', data.error);
return showFallbackProducts();
}
return data;
} catch (error) {
// Handle network errors
console.error('Network error:', error);
return showFallbackProducts();
}
Troubleshooting
No recommendations returned
- Check App ID - Verify you're using the correct App ID
- Product feed - Ensure products exist in your edrone product feed
- Interaction history - Send at least some product interactions
- Product IDs match - Verify product IDs match your product feed
Recommendations not personalized
- Send more interaction history (minimum 3-5 interactions)
- Include both
product_viewandadd_to_cartevents - Verify
cIdis consistent across sessions
Slow response times
- Limit interaction history to last 100 items
- Implement client-side caching
- Use loading states for better UX
Related resources
- Trace API - For tracking user events
- Product Feed - Setting up your product catalog
- Frontend Integration - Complete frontend setup