npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@shopsavvy/sdk

v1.0.2

Published

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

Downloads

117

Readme

🛍️ 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('MQ023LL/A')  // iPhone model number

// 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 | [email protected] | 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 [email protected] or visit shopsavvy.com/data for more information.


Made with ❤️ by the ShopSavvy Team

WebsiteAPI DocsDashboardSupport