@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
Markdown
# ๐๏ธ ShopSavvy Data API - TypeScript/JavaScript SDK
[](https://badge.fury.io/js/@shopsavvy/sdk)
[](https://opensource.org/licenses/MIT)
[](https://www.npmjs.com/package/@shopsavvy/sdk)
[](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>