The most comprehensive TypeScript/JavaScript SDK for e-commerce product data and pricing intelligence.
Access real-time product information, pricing data, and historical trends across thousands of retailers and millions of products with the official ShopSavvy Data API.
npm install @shopsavvy/sdk
# or
yarn add @shopsavvy/sdk
# or
pnpm add @shopsavvy/sdk
# or
bun add @shopsavvy/sdk- π Visit shopsavvy.com/data
- π Sign up for a free account
- π³ Choose a subscription plan
- π Get your API key from the dashboard
import { createShopSavvyClient } from '@shopsavvy/sdk'
// Initialize the client
const api = createShopSavvyClient({
apiKey: 'ss_live_your_api_key_here'
})
// Look up any product by barcode, ASIN, or URL
const product = await api.getProductDetails('012345678901')
console.log(`π¦ ${product.data.name} by ${product.data.brand}`)
// Get current prices from all retailers
const offers = await api.getCurrentOffers('012345678901')
const cheapest = offers.data.reduce((min, offer) => offer.price < min.price ? offer : min)
console.log(`π° Best price: $${cheapest.price} at ${cheapest.retailer}`)
// Set up price monitoring
await api.scheduleProductMonitoring('012345678901', 'daily')
console.log('π Price alerts activated!')| Feature | Description | Use Cases |
|---|---|---|
| π Universal Product Lookup | Search by barcode, ASIN, URL, model number | Product catalogs, inventory management |
| π² Real-Time Pricing | Current prices across major retailers | Price comparison, competitive analysis |
| π Historical Data | Price trends and availability over time | Market research, pricing strategy |
| π Smart Monitoring | Automated price tracking and alerts | Price drops, stock notifications |
| πͺ Multi-Retailer Support | Amazon, Walmart, Target, Best Buy + more | Comprehensive market coverage |
| β‘ Batch Operations | Process multiple products efficiently | Bulk analysis, data processing |
| π‘οΈ Full TypeScript | Complete type definitions and intellisense | Type-safe development |
| π Multiple Formats | JSON and CSV response options | Easy data integration |
npm install @shopsavvy/sdkgit clone https://github.com/shopsavvy/sdk-typescript
cd sdk-typescript
npm install
npm run build# Optional: Store your API key securely
export SHOPSAVVY_API_KEY="ss_live_your_api_key_here"import { createShopSavvyClient } from '@shopsavvy/sdk'
// Basic setup
const api = createShopSavvyClient({
apiKey: 'ss_live_your_api_key_here'
})
// With custom timeout and base URL
const api = createShopSavvyClient({
apiKey: 'ss_live_your_api_key_here',
timeout: 60000,
baseUrl: 'https://api.shopsavvy.com/v1'
})import { ShopSavvyDataAPI } from '@shopsavvy/sdk'
const api = new ShopSavvyDataAPI({
apiKey: 'ss_live_your_api_key_here',
timeout: 45000
})// Load API key from environment
const api = createShopSavvyClient({
apiKey: process.env.SHOPSAVVY_API_KEY || 'ss_live_your_api_key_here'
})// Search by barcode (UPC/EAN)
const product = await api.getProductDetails('012345678901')
// Search by Amazon ASIN
const amazonProduct = await api.getProductDetails('B08N5WRWNW')
// Search by product URL
const urlProduct = await api.getProductDetails('https://www.amazon.com/dp/B08N5WRWNW')
// Search by model number
const modelProduct = await api.getProductDetails('iPhone-14-Pro')
// Access product information
console.log(`π¦ Product: ${product.data.name}`)
console.log(`π·οΈ Brand: ${product.data.brand}`)
console.log(`π Category: ${product.data.category}`)
console.log(`π’ Product ID: ${product.data.product_id}`)
console.log(`π· Image: ${product.data.image_url}`)// Look up multiple products at once
const identifiers = [
'012345678901', // Barcode
'B08N5WRWNW', // Amazon ASIN
'https://www.target.com/p/example', // URL
'MODEL-ABC123' // Model number
]
const products = await api.getProductDetailsBatch(identifiers)
for (const product of products.data) {
console.log(`β
Found: ${product.name} by ${product.brand}`)
console.log(` ID: ${product.product_id}`)
console.log(` Category: ${product.category}`)
console.log('---')
}// Get product data in CSV format for easy processing
const productCsv = await api.getProductDetails('012345678901', { format: 'csv' })
// Process CSV data (example with a CSV parsing library)
// import { parse } from 'csv-parse/sync'
// const records = parse(productCsv.data, { columns: true, skip_empty_lines: true })// Get prices from all retailers
const offers = await api.getCurrentOffers('012345678901')
console.log(`Found ${offers.data.length} offers:`)
for (const offer of offers.data) {
console.log(`πͺ ${offer.retailer}: $${offer.price}`)
console.log(` π¦ Condition: ${offer.condition}`)
console.log(` β
Available: ${offer.availability}`)
console.log(` π Buy: ${offer.url}`)
if (offer.shipping) {
console.log(` π Shipping: $${offer.shipping}`)
}
console.log('---')
}// Get offers from specific retailers
const amazonOffers = await api.getCurrentOffers('012345678901', { retailer: 'amazon' })
const walmartOffers = await api.getCurrentOffers('012345678901', { retailer: 'walmart' })
const targetOffers = await api.getCurrentOffers('012345678901', { retailer: 'target' })
console.log('Amazon prices:')
for (const offer of amazonOffers.data) {
console.log(` $${offer.price} - ${offer.condition}`)
}// Get current offers for multiple products
const products = ['012345678901', 'B08N5WRWNW', '045496596439']
const batchOffers = await api.getCurrentOffersBatch(products)
for (const [identifier, offers] of Object.entries(batchOffers.data)) {
const bestPrice = offers.length > 0 ? Math.min(...offers.map(o => o.price)) : null
const bestOffer = offers.find(o => o.price === bestPrice)
if (bestOffer) {
console.log(`${identifier}: Best price $${bestOffer.price} at ${bestOffer.retailer}`)
} else {
console.log(`${identifier}: No offers found`)
}
}// Get 30 days of price history
const endDate = new Date().toISOString().split('T')[0]
const startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]
const history = await api.getPriceHistory('012345678901', startDate, endDate)
for (const offer of history.data) {
console.log(`πͺ ${offer.retailer}:`)
console.log(` π° Current price: $${offer.price}`)
console.log(` π Historical points: ${offer.price_history.length}`)
if (offer.price_history.length > 0) {
const prices = offer.price_history.map(point => point.price)
console.log(` π Lowest: $${Math.min(...prices)}`)
console.log(` π Highest: $${Math.max(...prices)}`)
console.log(` π Average: $${(prices.reduce((a, b) => a + b, 0) / prices.length).toFixed(2)}`)
}
console.log('---')
}// Get price history from Amazon only
const amazonHistory = await api.getPriceHistory(
'012345678901',
'2024-01-01',
'2024-01-31',
{ retailer: 'amazon' }
)
for (const offer of amazonHistory.data) {
console.log(`Amazon price trends for ${offer.retailer}:`)
// Last 10 data points
const recentHistory = offer.price_history.slice(-10)
for (const point of recentHistory) {
console.log(` ${point.date}: $${point.price} (${point.availability})`)
}
}// Monitor daily across all retailers
const result = await api.scheduleProductMonitoring('012345678901', 'daily')
if (result.data.scheduled) {
console.log('β
Daily monitoring activated!')
}
// Monitor hourly at specific retailer
const amazonResult = await api.scheduleProductMonitoring(
'012345678901',
'hourly',
{ retailer: 'amazon' }
)
console.log(`Amazon monitoring: ${JSON.stringify(amazonResult.data)}`)// Schedule multiple products for monitoring
const productsToMonitor = [
'012345678901',
'B08N5WRWNW',
'045496596439'
]
const batchResult = await api.scheduleProductMonitoringBatch(productsToMonitor, 'daily')
for (const item of batchResult.data) {
if (item.scheduled) {
console.log(`β
Monitoring activated for ${item.identifier}`)
} else {
console.log(`β Failed to monitor ${item.identifier}`)
}
}// View all monitored products
const scheduled = await api.getScheduledProducts()
console.log(`π Currently monitoring ${scheduled.data.length} products:`)
for (const product of scheduled.data) {
console.log(`π ${product.identifier}`)
console.log(` π
Frequency: ${product.frequency}`)
console.log(` πͺ Retailer: ${product.retailer || 'All retailers'}`)
console.log(` π
Created: ${product.created_at}`)
if (product.last_refreshed) {
console.log(` π Last refresh: ${product.last_refreshed}`)
}
console.log('---')
}
// Remove products from monitoring
await api.removeProductFromSchedule('012345678901')
console.log('ποΈ Removed from monitoring')
// Remove multiple products
await api.removeProductsFromSchedule(['012345678901', 'B08N5WRWNW'])
console.log('ποΈ Batch removal complete')// Check your API usage
const usage = await api.getUsage()
console.log('π API Usage Summary:')
console.log(`π³ Plan: ${usage.data.plan_name}`)
console.log(`β
Credits used: ${usage.data.credits_used.toLocaleString()}`)
console.log(`π Credits remaining: ${usage.data.credits_remaining.toLocaleString()}`)
console.log(`π Total credits: ${usage.data.credits_total.toLocaleString()}`)
console.log(`π
Billing period: ${usage.data.billing_period_start} to ${usage.data.billing_period_end}`)
// Calculate usage percentage
const usagePercent = (usage.data.credits_used / usage.data.credits_total) * 100
console.log(`π Usage: ${usagePercent.toFixed(1)}%`)interface DealResult {
identifier: string
productName: string
brand: string
offers: Array<{
retailer: string
price: number
totalCost: number
condition: string
url: string
shipping?: number
}>
bestOffer: {
retailer: string
price: number
totalCost: number
url: string
}
savings: number
}
async function findBestDeals(identifier: string, maxResults = 5): Promise<DealResult | null> {
try {
// Get product info
const product = await api.getProductDetails(identifier)
console.log(`π Searching deals for: ${product.data.name}`)
console.log(`π¦ Brand: ${product.data.brand}`)
console.log('='.repeat(50))
// Get all current offers
const offers = await api.getCurrentOffers(identifier)
if (offers.data.length === 0) {
console.log('β No offers found')
return null
}
// Filter and sort offers
const availableOffers = offers.data.filter(offer => offer.availability === 'in_stock')
if (availableOffers.length === 0) {
console.log('β No in-stock offers found')
return null
}
// Calculate total cost (price + shipping)
const offersWithTotal = availableOffers.map(offer => ({
...offer,
totalCost: offer.price + (offer.shipping || 0)
}))
const sortedOffers = offersWithTotal
.sort((a, b) => a.totalCost - b.totalCost)
.slice(0, maxResults)
console.log(`π Top ${sortedOffers.length} Deals:`)
sortedOffers.forEach((offer, i) => {
console.log(`${i + 1}. πͺ ${offer.retailer}`)
console.log(` π° Price: $${offer.price}`)
if (offer.shipping) {
console.log(` π Shipping: $${offer.shipping}`)
}
console.log(` π³ Total: $${offer.totalCost}`)
console.log(` π¦ Condition: ${offer.condition}`)
console.log(` π Buy now: ${offer.url}`)
console.log('---')
})
// Calculate savings
const savings = sortedOffers.length > 1
? sortedOffers[sortedOffers.length - 1].totalCost - sortedOffers[0].totalCost
: 0
if (savings > 0) {
console.log(`π° Potential savings: $${savings.toFixed(2)}`)
}
return {
identifier,
productName: product.data.name,
brand: product.data.brand || '',
offers: sortedOffers.map(o => ({
retailer: o.retailer,
price: o.price,
totalCost: o.totalCost,
condition: o.condition,
url: o.url,
shipping: o.shipping
})),
bestOffer: {
retailer: sortedOffers[0].retailer,
price: sortedOffers[0].price,
totalCost: sortedOffers[0].totalCost,
url: sortedOffers[0].url
},
savings
}
} catch (error) {
console.error(`β Error: ${error instanceof Error ? error.message : error}`)
return null
}
}
// Usage
const deals = await findBestDeals('012345678901')interface PriceAlert {
identifier: string
targetPrice: number
currentPrice?: number
lastChecked?: Date
}
class PriceAlertBot {
private alerts: Map<string, PriceAlert> = new Map()
constructor(private api: ShopSavvyDataAPI) {}
async addAlert(identifier: string, targetPrice: number): Promise<void> {
this.alerts.set(identifier, { identifier, targetPrice })
// Schedule monitoring
await this.api.scheduleProductMonitoring(identifier, 'daily')
console.log(`π Alert set: ${identifier} @ $${targetPrice}`)
}
async checkAlerts(): Promise<void> {
console.log(`π Checking ${this.alerts.size} price alerts...`)
for (const [identifier, alert] of this.alerts) {
try {
const offers = await this.api.getCurrentOffers(identifier)
if (offers.data.length === 0) continue
// Find best available offer
const inStockOffers = offers.data.filter(o => o.availability === 'in_stock')
if (inStockOffers.length === 0) continue
const bestOffer = inStockOffers.reduce((min, offer) =>
offer.price < min.price ? offer : min
)
// Update alert with current price
alert.currentPrice = bestOffer.price
alert.lastChecked = new Date()
if (bestOffer.price <= alert.targetPrice) {
await this.triggerAlert(identifier, bestOffer, alert.targetPrice)
}
} catch (error) {
console.error(`β Error checking ${identifier}: ${error instanceof Error ? error.message : error}`)
}
}
}
private async triggerAlert(identifier: string, offer: any, targetPrice: number): Promise<void> {
const product = await this.api.getProductDetails(identifier)
console.log('π¨'.repeat(10))
console.log('π° PRICE ALERT TRIGGERED!')
console.log(`π¦ Product: ${product.data.name}`)
console.log(`π― Target: $${targetPrice}`)
console.log(`πΈ Current: $${offer.price} at ${offer.retailer}`)
console.log(`β
Savings: $${(targetPrice - offer.price).toFixed(2)}`)
console.log(`π Buy now: ${offer.url}`)
console.log('π¨'.repeat(10))
// Remove alert after triggering
this.alerts.delete(identifier)
}
getAlerts(): PriceAlert[] {
return Array.from(this.alerts.values())
}
removeAlert(identifier: string): boolean {
return this.alerts.delete(identifier)
}
}
// Usage
const alertBot = new PriceAlertBot(api)
await alertBot.addAlert('012345678901', 199.99)
await alertBot.addAlert('B08N5WRWNW', 299.99)
// Run periodic checks
await alertBot.checkAlerts()interface RetailerStats {
currentPrice: number
avgPrice: number
minPrice: number
maxPrice: number
volatility: number
dataPoints: number
trend: string
}
interface MarketAnalysis {
identifier: string
productName: string
brand: string
category: string
retailerStats: Record<string, RetailerStats>
bestRetailer: {
name: string
price: number
} | null
}
async function analyzeMarketTrends(identifiers: string[], days = 30): Promise<MarketAnalysis[]> {
const endDate = new Date().toISOString().split('T')[0]
const startDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0]
console.log(`π Market Analysis Report (${days} days)`)
console.log('='.repeat(50))
const results: MarketAnalysis[] = []
for (const identifier of identifiers) {
try {
// Get product info
const product = await api.getProductDetails(identifier)
console.log(`\\nπ¦ ${product.data.name}`)
console.log(`π·οΈ ${product.data.brand} | ${product.data.category}`)
console.log('-'.repeat(40))
// Get price history
const history = await api.getPriceHistory(identifier, startDate, endDate)
const retailerStats: Record<string, RetailerStats> = {}
for (const offer of history.data) {
if (offer.price_history.length === 0) continue
const prices = offer.price_history.map(point => point.price)
const avgPrice = prices.reduce((a, b) => a + b, 0) / prices.length
const variance = prices.reduce((a, b) => a + Math.pow(b - avgPrice, 2), 0) / prices.length
retailerStats[offer.retailer] = {
currentPrice: offer.price,
avgPrice,
minPrice: Math.min(...prices),
maxPrice: Math.max(...prices),
volatility: Math.sqrt(variance),
dataPoints: prices.length,
trend: calculateTrend(prices)
}
}
// Display results
if (Object.keys(retailerStats).length > 0) {
console.log('πͺ Retailer Analysis:')
for (const [retailer, stats] of Object.entries(retailerStats)) {
console.log(` ${retailer}:`)
console.log(` π° Current: $${stats.currentPrice}`)
console.log(` π Average: $${stats.avgPrice.toFixed(2)}`)
console.log(` π Min: $${stats.minPrice} | π Max: $${stats.maxPrice}`)
console.log(` π Trend: ${stats.trend}`)
console.log(` π Data points: ${stats.dataPoints}`)
}
// Find best value
const bestRetailerEntry = Object.entries(retailerStats)
.reduce((min, [name, stats]) => stats.currentPrice < min[1].currentPrice ? [name, stats] : min)
console.log(`\\nπ Best Price: ${bestRetailerEntry[0]} @ $${bestRetailerEntry[1].currentPrice}`)
results.push({
identifier,
productName: product.data.name,
brand: product.data.brand || '',
category: product.data.category || '',
retailerStats,
bestRetailer: {
name: bestRetailerEntry[0],
price: bestRetailerEntry[1].currentPrice
}
})
} else {
console.log('β No price history available')
results.push({
identifier,
productName: product.data.name,
brand: product.data.brand || '',
category: product.data.category || '',
retailerStats: {},
bestRetailer: null
})
}
} catch (error) {
console.error(`β Error analyzing ${identifier}: ${error instanceof Error ? error.message : error}`)
}
}
return results
}
function calculateTrend(prices: number[]): string {
if (prices.length < 2) return 'Unknown'
const recent = prices.slice(-7) // Last week
const older = prices.slice(0, -7) // Everything else
if (older.length === 0) return 'New'
const recentAvg = recent.reduce((a, b) => a + b, 0) / recent.length
const olderAvg = older.reduce((a, b) => a + b, 0) / older.length
if (recentAvg > olderAvg * 1.05) { // 5% threshold
return 'π Rising'
} else if (recentAvg < olderAvg * 0.95) {
return 'π Falling'
} else {
return 'β‘οΈ Stable'
}
}
// Usage
const productsToAnalyze = [
'012345678901',
'B08N5WRWNW',
'045496596439'
]
const analysis = await analyzeMarketTrends(productsToAnalyze, 60)interface BulkProductData {
identifier: string
targetPrice: number
monitor: boolean
}
interface BulkAnalysisResult {
identifier: string
name: string
brand: string
targetPrice: number
currentBestPrice: number | null
priceAlert: boolean
offersCount: number
}
async function bulkProductManager(csvData: BulkProductData[]): Promise<BulkAnalysisResult[]> {
console.log('π Loading products from data...')
console.log(`π Processing ${csvData.length} products...`)
// Batch lookup
const identifiers = csvData.map(p => p.identifier)
try {
const [productDetails, currentOffers] = await Promise.all([
api.getProductDetailsBatch(identifiers),
api.getCurrentOffersBatch(identifiers)
])
const results: BulkAnalysisResult[] = []
for (let i = 0; i < csvData.length; i++) {
const productData = csvData[i]
const details = productDetails.data[i]
const offers = currentOffers.data[productData.identifier] || []
const inStockOffers = offers.filter(o => o.availability === 'in_stock')
const bestPrice = inStockOffers.length > 0
? Math.min(...inStockOffers.map(o => o.price))
: null
const result: BulkAnalysisResult = {
identifier: productData.identifier,
name: details.name,
brand: details.brand || '',
targetPrice: productData.targetPrice,
currentBestPrice: bestPrice,
priceAlert: bestPrice !== null && bestPrice <= productData.targetPrice,
offersCount: offers.length
}
results.push(result)
// Setup monitoring if requested
if (productData.monitor) {
await api.scheduleProductMonitoring(productData.identifier, 'daily')
}
}
// Generate report
console.log('\\nπ Bulk Analysis Report:')
console.log('='.repeat(80))
for (const result of results) {
const status = result.priceAlert ? 'π¨ ALERT' : 'π TRACKING'
console.log(`${status} | ${result.name} by ${result.brand}`)
console.log(` π― Target: $${result.targetPrice} | π° Current: ${result.currentBestPrice ? `$${result.currentBestPrice}` : 'N/A'}`)
console.log(` πͺ Offers: ${result.offersCount}`)
console.log()
}
return results
} catch (error) {
console.error(`β Error processing bulk products: ${error instanceof Error ? error.message : error}`)
return []
}
}
// Usage with sample data
const sampleData: BulkProductData[] = [
{ identifier: '012345678901', targetPrice: 199.99, monitor: true },
{ identifier: 'B08N5WRWNW', targetPrice: 299.99, monitor: true },
{ identifier: '045496596439', targetPrice: 49.99, monitor: false }
]
const bulkResults = await bulkProductManager(sampleData)interface ExportData {
exportedAt: string
products: Array<{
product: any
offers: any[]
}>
}
async function exportProductData(identifiers: string[], format: 'json' | 'csv' = 'json'): Promise<void> {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0] + '_' +
new Date().toTimeString().split(' ')[0].replace(/:/g, '')
if (format.toLowerCase() === 'json') {
// Export as JSON
const [products, offers] = await Promise.all([
api.getProductDetailsBatch(identifiers),
api.getCurrentOffersBatch(identifiers)
])
const exportData: ExportData = {
exportedAt: new Date().toISOString(),
products: products.data.map(product => ({
product,
offers: offers.data[product.product_id] || []
}))
}
const filename = `shopsavvy_export_${timestamp}.json`
// In Node.js environment
if (typeof window === 'undefined') {
const fs = await import('fs')
fs.writeFileSync(filename, JSON.stringify(exportData, null, 2))
} else {
// In browser environment
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
a.click()
URL.revokeObjectURL(url)
}
console.log(`β
Exported ${products.data.length} products to ${filename}`)
} else if (format.toLowerCase() === 'csv') {
// Export as CSV
const filename = `shopsavvy_export_${timestamp}.csv`
const csvRows = ['product_id,name,brand,category,barcode,retailer,price,availability,condition,url']
for (const identifier of identifiers) {
try {
const [product, offers] = await Promise.all([
api.getProductDetails(identifier),
api.getCurrentOffers(identifier)
])
for (const offer of offers.data) {
const row = [
product.data.product_id,
`"${product.data.name}"`,
`"${product.data.brand || ''}"`,
`"${product.data.category || ''}"`,
product.data.barcode || '',
offer.retailer,
offer.price,
offer.availability,
offer.condition,
`"${offer.url}"`
].join(',')
csvRows.push(row)
}
} catch (error) {
console.error(`β Error exporting ${identifier}: ${error instanceof Error ? error.message : error}`)
}
}
const csvContent = csvRows.join('\\n')
// In Node.js environment
if (typeof window === 'undefined') {
const fs = await import('fs')
fs.writeFileSync(filename, csvContent)
} else {
// In browser environment
const blob = new Blob([csvContent], { type: 'text/csv' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
a.click()
URL.revokeObjectURL(url)
}
console.log(`β
Exported data to ${filename}`)
}
}
// Usage
await exportProductData(['012345678901', 'B08N5WRWNW'], 'json')
await exportProductData(['012345678901', 'B08N5WRWNW'], 'csv')// Custom error types for better error handling
class ShopSavvyError extends Error {
constructor(
message: string,
public statusCode?: number,
public errorCode?: string
) {
super(message)
this.name = 'ShopSavvyError'
}
}
async function robustProductLookup(identifier: string): Promise<{ product: any; offers: any[] } | null> {
try {
const [product, offers] = await Promise.all([
api.getProductDetails(identifier),
api.getCurrentOffers(identifier)
])
console.log(`β
Success: ${product.data.name}`)
console.log(`π° Found ${offers.data.length} offers`)
return { product: product.data, offers: offers.data }
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
if (errorMessage.includes('Invalid API key')) {
console.error('β Authentication failed - check your API key')
console.log('π Get your key at: https://shopsavvy.com/data/dashboard')
} else if (errorMessage.includes('Product not found') || errorMessage.includes('404')) {
console.error(`β Product not found: ${identifier}`)
console.log('π‘ Try a different identifier (barcode, ASIN, URL)')
} else if (errorMessage.includes('Rate limit') || errorMessage.includes('429')) {
console.error('β³ Rate limit exceeded - please slow down')
console.log('π‘ Consider upgrading your plan for higher limits')
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, 60000))
} else if (errorMessage.includes('timeout')) {
console.error('β±οΈ Request timeout - API might be slow')
console.log('π‘ Try increasing timeout or retry later')
} else if (errorMessage.includes('network') || errorMessage.includes('fetch')) {
console.error(`π Network error: ${errorMessage}`)
console.log('π‘ Check your internet connection')
} else {
console.error(`π¨ API Error: ${errorMessage}`)
console.log('π‘ This might be a temporary issue')
}
return null
}
}
// Usage with retry logic
async function lookupWithRetry(identifier: string, maxRetries = 3): Promise<any> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await api.getProductDetails(identifier)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
if (errorMessage.includes('timeout') || errorMessage.includes('network')) {
if (attempt < maxRetries - 1) {
const waitTime = Math.pow(2, attempt) * 1000 // Exponential backoff
console.log(`β³ Retry ${attempt + 1}/${maxRetries} in ${waitTime}ms...`)
await new Promise(resolve => setTimeout(resolve, waitTime))
} else {
throw error
}
} else {
throw error // Don't retry for non-transient errors
}
}
}
}class RateLimiter {
private lastCallTime = 0
private readonly minInterval: number
constructor(callsPerSecond = 10) {
this.minInterval = 1000 / callsPerSecond
}
async throttle<T>(fn: () => Promise<T>): Promise<T> {
const elapsed = Date.now() - this.lastCallTime
const leftToWait = this.minInterval - elapsed
if (leftToWait > 0) {
await new Promise(resolve => setTimeout(resolve, leftToWait))
}
this.lastCallTime = Date.now()
return fn()
}
}
// Rate-limited API calls
const rateLimiter = new RateLimiter(5) // Max 5 calls per second
async function safeGetOffers(identifier: string) {
return rateLimiter.throttle(() => api.getCurrentOffers(identifier))
}
// Batch processing with rate limiting
async function processProductsSafely(identifiers: string[]): Promise<Array<{ identifier: string; offers: any[] | null }>> {
const results: Array<{ identifier: string; offers: any[] | null }> = []
for (let i = 0; i < identifiers.length; i++) {
const identifier = identifiers[i]
console.log(`π Processing ${i + 1}/${identifiers.length}: ${identifier}`)
try {
const offers = await safeGetOffers(identifier)
results.push({ identifier, offers: offers.data })
console.log(` β
Found ${offers.data.length} offers`)
} catch (error) {
console.error(` β Error: ${error instanceof Error ? error.message : error}`)
results.push({ identifier, offers: null })
}
}
return results
}interface ShopSavvySettings {
apiKey: string
timeout: number
maxRetries: number
rateLimit: number // calls per second
baseUrl?: string
}
class ConfigManager {
private settings: ShopSavvySettings
constructor() {
this.settings = this.loadFromEnvironment()
}
private loadFromEnvironment(): ShopSavvySettings {
const apiKey = process.env.SHOPSAVVY_API_KEY
if (!apiKey) {
throw new Error('SHOPSAVVY_API_KEY environment variable required')
}
return {
apiKey,
timeout: parseInt(process.env.SHOPSAVVY_TIMEOUT || '30000'),
maxRetries: parseInt(process.env.SHOPSAVVY_MAX_RETRIES || '3'),
rateLimit: parseFloat(process.env.SHOPSAVVY_RATE_LIMIT || '10'),
baseUrl: process.env.SHOPSAVVY_BASE_URL
}
}
getSettings(): ShopSavvySettings {
return { ...this.settings }
}
createClient(): ShopSavvyDataAPI {
return createShopSavvyClient({
apiKey: this.settings.apiKey,
timeout: this.settings.timeout,
baseUrl: this.settings.baseUrl
})
}
}
// Usage
const config = new ConfigManager()
const api = config.createClient()# Install development dependencies
npm install
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run with coverage
npm test -- --coverage
# Run specific test file
npm test -- --testPathPattern=client.test.ts
# Run with verbose output
npm test -- --verbose# Lint code
npm run lint
# Build the project
npm run build
# Watch for changes during development
npm run devimport { createShopSavvyClient, ShopSavvyDataAPI } from '../src/index'
// Mock fetch for testing
global.fetch = jest.fn()
describe('ShopSavvy SDK', () => {
let api: ShopSavvyDataAPI
const mockFetch = fetch as jest.MockedFunction<typeof fetch>
beforeEach(() => {
api = createShopSavvyClient({
apiKey: 'ss_test_valid_key'
})
mockFetch.mockClear()
})
it('should create client with valid API key', () => {
expect(api).toBeInstanceOf(ShopSavvyDataAPI)
})
it('should throw error for invalid API key', () => {
expect(() => {
createShopSavvyClient({ apiKey: 'invalid_key_format' })
}).toThrow('Invalid API key format')
})
it('should fetch product details successfully', async () => {
// Mock successful response
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
success: true,
data: {
product_id: '12345',
name: 'Test Product',
brand: 'Test Brand'
}
})
} as Response)
const product = await api.getProductDetails('012345678901')
expect(product.success).toBe(true)
expect(product.data.name).toBe('Test Product')
expect(mockFetch).toHaveBeenCalledTimes(1)
})
it('should handle API errors gracefully', async () => {
// Mock error response
mockFetch.mockResolvedValueOnce({
ok: false,
status: 404,
statusText: 'Not Found',
json: async () => ({ error: 'Product not found' })
} as Response)
await expect(api.getProductDetails('invalid-id')).rejects.toThrow('Product not found')
})
})FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
RUN npm ci --only=production
# Copy application code
COPY dist/ ./dist/
# Set environment variables
ENV SHOPSAVVY_API_KEY="your_api_key_here"
ENV SHOPSAVVY_TIMEOUT="30000"
# Expose port
EXPOSE 3000
# Run application
CMD ["node", "dist/index.js"]import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'
import { createShopSavvyClient } from '@shopsavvy/sdk'
// Initialize client outside handler for connection reuse
const api = createShopSavvyClient({
apiKey: process.env.SHOPSAVVY_API_KEY!
})
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
const identifier = event.queryStringParameters?.identifier
if (!identifier) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'identifier parameter required' })
}
}
// Get product data
const [product, offers] = await Promise.all([
api.getProductDetails(identifier),
api.getCurrentOffers(identifier)
])
// Find best price
const inStockOffers = offers.data.filter(o => o.availability === 'in_stock')
const bestOffer = inStockOffers.length > 0
? inStockOffers.reduce((min, offer) => offer.price < min.price ? offer : min)
: null
const response = {
product: {
name: product.data.name,
brand: product.data.brand,
category: product.data.category
},
best_price: bestOffer ? {
price: bestOffer.price,
retailer: bestOffer.retailer,
url: bestOffer.url
} : null,
total_offers: offers.data.length
}
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(response)
}
} catch (error) {
console.error('Lambda error:', error)
return {
statusCode: 500,
body: JSON.stringify({
error: error instanceof Error ? error.message : 'Internal server error'
})
}
}
}# Required
export SHOPSAVVY_API_KEY="ss_live_your_api_key_here"
# Optional
export SHOPSAVVY_TIMEOUT="30000"
export SHOPSAVVY_BASE_URL="https://api.shopsavvy.com/v1"
export SHOPSAVVY_MAX_RETRIES="3"class EcommerceIntegration {
constructor(private api: ShopSavvyDataAPI) {}
async enrichProductCatalog(productSkus: string[]): Promise<any[]> {
const enrichedProducts = []
for (const sku of productSkus) {
try {
// Get competitive pricing
const offers = await this.api.getCurrentOffers(sku)
const competitorPrices = offers.data
.filter(offer => offer.retailer !== 'your-store')
.map(offer => offer.price)
const enrichment = {
sku,
competitor_count: competitorPrices.length,
min_competitor_price: competitorPrices.length > 0 ? Math.min(...competitorPrices) : null,
avg_competitor_price: competitorPrices.length > 0 ?
competitorPrices.reduce((a, b) => a + b, 0) / competitorPrices.length : null,
price_position: this.calculatePricePosition(sku, competitorPrices)
}
enrichedProducts.push(enrichment)
} catch (error) {
console.error(`β Error enriching ${sku}: ${error instanceof Error ? error.message : error}`)
}
}
return enrichedProducts
}
private calculatePricePosition(sku: string, competitorPrices: number[]): string {
if (competitorPrices.length === 0) return 'no_competition'
const yourPrice = this.getYourPrice(sku) // Your implementation
if (!yourPrice) return 'unknown'
const cheaperCount = competitorPrices.filter(price => price < yourPrice).length
const totalCompetitors = competitorPrices.length
if (cheaperCount === 0) return 'most_expensive'
if (cheaperCount === totalCompetitors) return 'cheapest'
if (cheaperCount < totalCompetitors / 3) return 'premium'
if (cheaperCount > totalCompetitors * 2/3) return 'budget'
return 'competitive'
}
private getYourPrice(sku: string): number | null {
// Implement your price lookup logic
return null
}
}interface MarketInsights {
avgPriceTrend: number
topRetailers: Record<string, number>
availabilityBreakdown: Record<string, number>
marketVolatility: number
}
class BusinessIntelligenceDashboard {
constructor(private api: ShopSavvyDataAPI) {}
async generateMarketReport(category: string, timePeriodDays = 30): Promise<any> {
// Get category products (you'd have your own product database)
const categoryProducts = this.getCategoryProducts(category)
const report = {
category,
analysis_date: new Date().toISOString(),
time_period_days: timePeriodDays,
products_analyzed: categoryProducts.length,
insights: {} as MarketInsights
}
// Analyze each product
const priceTrends: number[] = []
const retailerCoverage = new Map<string, number>()
const availabilityStats = new Map<string, number>()
for (const productId of categoryProducts) {
try {
// Get current market state
const offers = await this.api.getCurrentOffers(productId)
for (const offer of offers.data) {
retailerCoverage.set(offer.retailer, (retailerCoverage.get(offer.retailer) || 0) + 1)
availabilityStats.set(offer.availability, (availabilityStats.get(offer.availability) || 0) + 1)
}
// Get price trends
const startDate = new Date(Date.now() - timePeriodDays * 24 * 60 * 60 * 1000).toISOString().split('T')[0]
const endDate = new Date().toISOString().split('T')[0]
const history = await this.api.getPriceHistory(productId, startDate, endDate)
for (const offerHistory of history.data) {
if (offerHistory.price_history.length > 0) {
const prices = offerHistory.price_history.map(p => p.price)
const trend = this.calculateTrendPercentage(prices)
priceTrends.push(trend)
}
}
} catch (error) {
console.error(`β Error analyzing ${productId}: ${error instanceof Error ? error.message : error}`)
}
}
// Compile insights
report.insights = {
avgPriceTrend: priceTrends.length > 0 ? priceTrends.reduce((a, b) => a + b, 0) / priceTrends.length : 0,
topRetailers: Object.fromEntries(
Array.from(retailerCoverage.entries())
.sort(([,a], [,b]) => b - a)
.slice(0, 10)
),
availabilityBreakdown: Object.fromEntries(availabilityStats),
marketVolatility: priceTrends.length > 1 ? this.calculateStandardDeviation(priceTrends) : 0
}
return report
}
private getCategoryProducts(category: string): string[] {
// Implement your product database lookup
return []
}
private calculateTrendPercentage(prices: number[]): number {
if (prices.length < 2) return 0
return ((prices[prices.length - 1] - prices[0]) / prices[0]) * 100
}
private calculateStandardDeviation(values: number[]): number {
const avg = values.reduce((a, b) => a + b, 0) / values.length
const squareDiffs = values.map(value => Math.pow(value - avg, 2))
const avgSquareDiff = squareDiffs.reduce((a, b) => a + b, 0) / squareDiffs.length
return Math.sqrt(avgSquareDiff)
}
}import express from 'express'
import cors from 'cors'
import { createShopSavvyClient } from '@shopsavvy/sdk'
const app = express()
const api = createShopSavvyClient({
apiKey: process.env.SHOPSAVVY_API_KEY!
})
app.use(cors())
app.use(express.json())
// Handle barcode scans from mobile app
app.post('/api/product/scan', async (req, res) => {
try {
const { barcode } = req.body
if (!barcode) {
return res.status(400).json({ error: 'Barcode required' })
}
// Get product details and offers in parallel
const [product, offers] = await Promise.all([
api.getProductDetails(barcode),
api.getCurrentOffers(barcode)
])
// Find best deals
const availableOffers = offers.data.filter(o => o.availability === 'in_stock')
const bestOffer = availableOffers.length > 0
? availableOffers.reduce((min, offer) => offer.price < min.price ? offer : min)
: null
const response = {
product: {
name: product.data.name,
brand: product.data.brand,
image_url: product.data.image_url,
category: product.data.category
},
pricing: {
best_price: bestOffer?.price || null,
best_retailer: bestOffer?.retailer || null,
buy_url: bestOffer?.url || null,
total_offers: availableOffers.length,
all_offers: availableOffers.slice(0, 5).map(offer => ({
retailer: offer.retailer,
price: offer.price,
availability: offer.availability,
condition: offer.condition,
url: offer.url
}))
}
}
res.json(response)
} catch (error) {
console.error('Scan error:', error)
res.status(500).json({
error: error instanceof Error ? error.message : 'Internal server error'
})
}
})
// Create price alert for mobile users
app.post('/api/product/alerts', async (req, res) => {
try {
const { product_id, target_price, user_id } = req.body
// Schedule monitoring
const result = await api.scheduleProductMonitoring(product_id, 'daily')
// Store alert in your database
// await storePrice Alert(user_id, product_id, target_price)
res.json({
success: true,
message: 'Price alert created successfully',
monitoring_active: result.data.scheduled
})
} catch (error) {
console.error('Alert creation error:', error)
res.status(500).json({
error: error instanceof Error ? error.message : 'Internal server error'
})
}
})
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() })
})
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
console.log(`π Server running on port ${PORT}`)
})We welcome contributions! See CONTRIBUTING.md for guidelines.
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes with tests
- Run the test suite:
npm test - Submit a pull request
| Resource | Link | Description |
|---|---|---|
| π API Documentation | shopsavvy.com/data/documentation | Complete API reference |
| π Dashboard | shopsavvy.com/data/dashboard | Manage your API keys and usage |
| π¬ Support | business@shopsavvy.com | Get help from our team |
| π Issues | GitHub Issues | Report bugs and request features |
| π¦ npm Package | npmjs.com/package/@shopsavvy/sdk | Package repository |
| π Changelog | GitHub Releases | Version history and updates |
This project is licensed under the MIT License - see the LICENSE file for details.
ShopSavvy has been helping shoppers save money since 2008. With over 40 million downloads and millions of active users, we're the most trusted name in price comparison and shopping intelligence.
Our Data API provides the same powerful product data and pricing intelligence that powers our consumer app, now available to developers and businesses worldwide.
- β 13+ Years of e-commerce data expertise
- β Millions of Products across thousands of retailers
- β Real-Time Data updated continuously
- β Enterprise Scale trusted by major brands
- β Developer Friendly with comprehensive tools and support
π Ready to get started? Get your API key and start building amazing e-commerce applications today!
π¬ Need help? Contact us at business@shopsavvy.com or visit shopsavvy.com/data for more information.