UNPKG

@shopsavvy/sdk

Version:

Official TypeScript/JavaScript SDK for ShopSavvy Data API - Access product data, pricing, and price history

1,640 lines (1,317 loc) โ€ข 48.8 kB
# ๐Ÿ›๏ธ ShopSavvy Data API - TypeScript/JavaScript SDK [![npm version](https://badge.fury.io/js/@shopsavvy/sdk.svg)](https://badge.fury.io/js/@shopsavvy/sdk) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Downloads](https://img.shields.io/npm/dm/@shopsavvy/sdk)](https://www.npmjs.com/package/@shopsavvy/sdk) [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/) **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](https://shopsavvy.com/data). --- ## ๐Ÿš€ Quick Start ### Installation ```bash npm install @shopsavvy/sdk # or yarn add @shopsavvy/sdk # or pnpm add @shopsavvy/sdk # or bun add @shopsavvy/sdk ``` ### Get Your API Key 1. ๐ŸŒŸ Visit [shopsavvy.com/data](https://shopsavvy.com/data) 2. ๐Ÿ“ Sign up for a free account 3. ๐Ÿ’ณ Choose a subscription plan 4. ๐Ÿ”‘ Get your API key from the dashboard ### 30-Second Example ```typescript 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!') ``` --- ## ๐ŸŽฏ Key Features | 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 | --- ## ๐Ÿ—๏ธ Installation & Setup ### Basic Installation ```bash npm install @shopsavvy/sdk ``` ### Development Installation ```bash git clone https://github.com/shopsavvy/sdk-typescript cd sdk-typescript npm install npm run build ``` ### Environment Setup ```bash # Optional: Store your API key securely export SHOPSAVVY_API_KEY="ss_live_your_api_key_here" ``` --- ## ๐Ÿ“– Complete API Reference ### ๐Ÿ”ง Client Configuration #### Method 1: Simple Client Creation (Recommended) ```typescript 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' }) ``` #### Method 2: Direct Class Instantiation ```typescript import { ShopSavvyDataAPI } from '@shopsavvy/sdk' const api = new ShopSavvyDataAPI({ apiKey: 'ss_live_your_api_key_here', timeout: 45000 }) ``` #### Method 3: Environment Variable Configuration ```typescript // Load API key from environment const api = createShopSavvyClient({ apiKey: process.env.SHOPSAVVY_API_KEY || 'ss_live_your_api_key_here' }) ``` ### ๐Ÿ” Product Lookup #### Single Product Lookup ```typescript // 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}`) ``` #### Batch Product Lookup ```typescript // 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('---') } ``` #### CSV Format Support ```typescript // 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 }) ``` ### ๐Ÿ’ฐ Current Pricing #### Get All Current Offers ```typescript // 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('---') } ``` #### Retailer-Specific Pricing ```typescript // 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}`) } ``` #### Batch Pricing ```typescript // 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`) } } ``` ### ๐Ÿ“ˆ Price History & Trends #### Basic Price History ```typescript // 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('---') } ``` #### Retailer-Specific History ```typescript // 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})`) } } ``` ### ๐Ÿ”” Product Monitoring & Alerts #### Schedule Single Product Monitoring ```typescript // 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)}`) ``` #### Batch Monitoring Setup ```typescript // 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}`) } } ``` #### Manage Scheduled Products ```typescript // 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') ``` ### ๐Ÿ“Š Usage & Analytics ```typescript // 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)}%`) ``` --- ## ๐Ÿ› ๏ธ Advanced Usage & Examples ### ๐Ÿ† Price Comparison Tool ```typescript 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') ``` ### ๐Ÿšจ Smart Price Alert System ```typescript 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() ``` ### ๐Ÿ“Š Market Analysis Dashboard ```typescript 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) ``` ### ๐Ÿ”„ Bulk Product Management ```typescript 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) ``` ### ๐ŸŒ Multi-Format Data Export ```typescript 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') ``` --- ## ๐Ÿ”ง Error Handling & Best Practices ### Comprehensive Error Handling ```typescript // 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 } } } } ``` ### Rate Limiting Best Practices ```typescript 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 } ``` ### Configuration Management ```typescript 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() ``` --- ## ๐Ÿงช Testing & Development ### Running Tests ```bash # 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 ``` ### Code Quality ```bash # Lint code npm run lint # Build the project npm run build # Watch for changes during development npm run dev ``` ### Example Test ```typescript import { 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') }) }) ``` --- ## ๐Ÿš€ Production Deployment ### Docker Setup ```dockerfile 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"] ``` ### AWS Lambda Example ```typescript 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' }) } } } ``` ### Environment Variables ```bash # 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" ``` --- ## ๐ŸŒŸ Real-World Use Cases ### ๐Ÿ›’ E-commerce Platform Integration ```typescript 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 } } ``` ### ๐Ÿข Business Intelligence Dashboard ```typescript 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) } } ``` ### ๐Ÿ“ฑ Express.js API Backend ```typescript 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}`) }) ``` --- ## ๐Ÿค Contributing We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. ### Quick Contribution Guide 1. **Fork the repository** 2. **Create a feature branch**: `git checkout -b feature/amazing-feature` 3. **Make your changes** with tests 4. **Run the test suite**: `npm test` 5. **Submit a pull request** --- ## ๐Ÿ“š Additional Resources | Resource | Link | Description | |----------|------|-------------| | ๐ŸŒ **API Documentation** | [shopsavvy.com/data/documentation](https://shopsavvy.com/data/documentation) | Complete API reference | | ๐Ÿ“Š **Dashboard** | [shopsavvy.com/data/dashboard](https://shopsavvy.com/data/dashboard) | Manage your API keys and usage | | ๐Ÿ’ฌ **Support** | [business@shopsavvy.com](mailto:business@shopsavvy.com) | Get help from our team | | ๐Ÿ› **Issues** | [GitHub Issues](https://github.com/shopsavvy/sdk-typescript/issues) | Report bugs and request features | | ๐Ÿ“ฆ **npm Package** | [npmjs.com/package/@shopsavvy/sdk](https://www.npmjs.com/package/@shopsavvy/sdk) | Package repository | | ๐Ÿ“– **Changelog** | [GitHub Releases](https://github.com/shopsavvy/sdk-typescript/releases) | Version history and updates | --- ## ๐Ÿ“„ License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. --- ## ๐Ÿข About ShopSavvy **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. ### Why Choose ShopSavvy? - โœ… **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](https://shopsavvy.com/data) and start building amazing e-commerce applications today! **๐Ÿ’ฌ Need help?** Contact us at [business@shopsavvy.com](mailto:business@shopsavvy.com) or visit [shopsavvy.com/data](https://shopsavvy.com/data) for more information. --- <div align="center"> **Made with โค๏ธ by the ShopSavvy Team** [Website](https://shopsavvy.com) โ€ข [API Docs](https://shopsavvy.com/data/documentation) โ€ข [Dashboard](https://shopsavvy.com/data/dashboard) โ€ข [Support](mailto:business@shopsavvy.com) </div>