Skip to content

shopsavvy/sdk-typescript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ›οΈ ShopSavvy Data API - TypeScript/JavaScript SDK

npm version License: MIT Downloads TypeScript

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.


πŸš€ Quick Start

Installation

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
  2. πŸ“ Sign up for a free account
  3. πŸ’³ Choose a subscription plan
  4. πŸ”‘ Get your API key from the dashboard

30-Second Example

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

npm install @shopsavvy/sdk

Development Installation

git clone https://github.com/shopsavvy/sdk-typescript
cd sdk-typescript
npm install
npm run build

Environment Setup

# 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)

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

import { ShopSavvyDataAPI } from '@shopsavvy/sdk'

const api = new ShopSavvyDataAPI({
  apiKey: 'ss_live_your_api_key_here',
  timeout: 45000
})

Method 3: Environment Variable Configuration

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

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

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

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

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

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

// 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

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

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

# 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

# Lint code
npm run lint

# Build the project
npm run build

# Watch for changes during development
npm run dev

Example Test

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

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

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

# 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

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

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

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 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 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

πŸ“„ License

This project is licensed under the MIT License - see the 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 and start building amazing e-commerce applications today!

πŸ’¬ Need help? Contact us at business@shopsavvy.com or visit shopsavvy.com/data for more information.


Made with ❀️ by the ShopSavvy Team

Website β€’ API Docs β€’ Dashboard β€’ Support

About

Official TypeScript/JavaScript SDK for ShopSavvy Data API

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published