Official Ruby SDK for the ShopSavvy Data API. Access comprehensive product data, real-time pricing, and historical price trends across tens of thousands of retailers and 30M+ products.
# Install
gem install shopsavvy-sdk
# Use
require 'shopsavvy_data_api'
client = ShopsavvyDataApi.new(api_key: 'ss_live_your_api_key_here')
product = client.get_product_details('012345678901')
puts "#{product.data.name} - Best price: $#{client.get_current_offers('012345678901').data.min_by(&:price).price}"Add to your Gemfile:
gem 'shopsavvy-sdk'Or install directly:
gem install shopsavvy-sdk- Sign up: Visit shopsavvy.com/data
- Choose plan: Select based on your usage needs
- Get API key: Copy from your dashboard
- Test: Run the 30-second example above
# For production, use environment variables
ENV['SHOPSAVVY_API_KEY'] = 'ss_live_your_api_key_here'
# Initialize client
client = ShopsavvyDataApi.new(api_key: ENV['SHOPSAVVY_API_KEY'])# Basic configuration
client = ShopsavvyDataApi.new(
api_key: 'ss_live_your_api_key_here',
timeout: 30, # Request timeout in seconds
base_url: 'https://api.shopsavvy.com/v1' # Custom base URL
)
# Advanced configuration with retry logic
client = ShopsavvyDataApi.new(
api_key: 'ss_live_your_api_key_here',
timeout: 60,
retry_attempts: 3,
retry_delay: 1.0,
user_agent: 'MyApp/1.0.0'
)
# Configuration object approach
config = ShopsavvyDataApi::Configuration.new(
api_key: 'ss_live_your_api_key_here',
timeout: 120,
debug: true # Enable debug logging
)
client = ShopsavvyDataApi.with_config(config)# Look up by barcode, ASIN, URL, model number, or ShopSavvy ID
product = client.get_product_details("012345678901")
amazon_product = client.get_product_details("B08N5WRWNW")
url_product = client.get_product_details("https://www.amazon.com/dp/B08N5WRWNW")
model_product = client.get_product_details("MQ023LL/A") # iPhone model number
puts "Product: #{product.data.name}"
puts "Brand: #{product.data.brand}"
puts "Category: #{product.data.category}"
puts "ASIN: #{product.data.asin}" if product.data.asin
puts "Model: #{product.data.model}" if product.data.model
puts "Description: #{product.data.description}" if product.data.description# Process up to 100 products per request
identifiers = [
"012345678901", "B08N5WRWNW", "045496590048",
"https://www.bestbuy.com/site/product/123456",
"MQ023LL/A", "SM-S911U" # iPhone and Samsung model numbers
]
products = client.get_product_details_batch(identifiers)
products.data.each do |product|
puts "#{product.name} by #{product.brand} - #{product.category}"
puts " Identifiers: #{product.identifiers}" if product.identifiers
end
# Handle potential errors in batch processing
products.data.each_with_index do |product, index|
if product.nil?
puts "Failed to find product: #{identifiers[index]}"
else
puts "β Found: #{product.name}"
end
endoffers = client.get_current_offers("012345678901")
puts "Found #{offers.data.length} offers across retailers"
# Advanced price analysis
sorted_offers = offers.data.sort_by(&:price)
cheapest = sorted_offers.first
most_expensive = sorted_offers.last
puts "π° Best price: #{cheapest.retailer} - $#{cheapest.price}"
puts "πΈ Highest price: #{most_expensive.retailer} - $#{most_expensive.price}"
puts "π Average price: $#{offers.data.map(&:price).sum / offers.data.length}"
puts "π‘ Potential savings: $#{most_expensive.price - cheapest.price}"
# Filter by availability and condition
in_stock_offers = offers.data.select(&:in_stock?) # availability == "in"
new_condition_offers = offers.data.select { |offer| offer.condition == 'new' }
puts "β
In-stock offers: #{in_stock_offers.length}"
puts "π New condition: #{new_condition_offers.length}"# Major retailers
amazon_offers = client.get_current_offers("012345678901", retailer: "amazon")
walmart_offers = client.get_current_offers("012345678901", retailer: "walmart")
target_offers = client.get_current_offers("012345678901", retailer: "target")
bestbuy_offers = client.get_current_offers("012345678901", retailer: "bestbuy")
# Compare specific retailers
retailers = %w[amazon.com walmart.com target.com bestbuy.com]
retailer_prices = {}
retailers.each do |retailer|
offers = client.get_current_offers("012345678901", retailer: retailer)
if offers.data.any?
best_offer = offers.data.min_by(&:price)
retailer_prices[retailer] = best_offer.price
end
end
puts "Retailer price comparison:"
retailer_prices.sort_by { |_, price| price }.each do |retailer, price|
puts " #{retailer.capitalize}: $#{price}"
end# Monitor multiple products simultaneously
product_list = [
"012345678901", "B08N5WRWNW", "045496590048",
"B07XJ8C8F5", "B09G9FPHY6"
]
batch_offers = client.get_current_offers_batch(product_list)
batch_offers.data.each do |identifier, offers|
next if offers.empty?
best_offer = offers.min_by(&:price)
puts "#{identifier}:"
puts " Best price: #{best_offer.retailer} - $#{best_offer.price}"
puts " Total offers: #{offers.length}"
puts " In stock: #{offers.count(&:in_stock?)}"
puts
endrequire 'date'
# Get 90 days of price history for detailed analysis
end_date = Date.today
start_date = end_date - 90
# Returns one entry per product found. Each product carries its offers, and each
# offer carries its `history` points, NEWEST FIRST: { timestamp, price, currency, availability }.
history = client.get_price_history(
"012345678901",
start_date.strftime("%Y-%m-%d"),
end_date.strftime("%Y-%m-%d")
)
puts "π 90-Day Price Analysis"
puts "=" * 50
product = history.data.first
puts product.title
product.offers.each do |offer|
next if offer.history.empty?
prices = offer.history.map(&:price) # newest first
current_price = offer.price
# Statistical analysis
avg_price = prices.sum.to_f / prices.length
min_price = prices.min
max_price = prices.max
# Price trend calculation (history is newest first)
recent_prices = prices.first(7)
older_prices = prices.drop(7)
trend = if recent_prices.any? && older_prices.any?
recent_avg = recent_prices.sum.to_f / recent_prices.length
older_avg = older_prices.sum.to_f / older_prices.length
change_pct = ((recent_avg - older_avg) / older_avg * 100).round(1)
if change_pct > 5
"π Rising (+#{change_pct}%)"
elsif change_pct < -5
"π Falling (#{change_pct}%)"
else
"π Stable (#{change_pct}%)"
end
else
"π Insufficient data"
end
puts "πͺ #{offer.retailer.upcase}"
puts " Current: $#{current_price}"
puts " Average: $#{avg_price.round(2)}"
puts " Range: $#{min_price} - $#{max_price}"
puts " Savings opportunity: $#{(current_price - min_price).round(2)}"
puts " Trend: #{trend}"
puts " Data points: #{offer.history.length}"
puts
end# Compare price history across major retailers
retailers = %w[amazon walmart target bestbuy]
historical_comparison = {}
retailers.each do |retailer|
history = client.get_price_history(
"012345678901",
"2024-01-01",
"2024-12-31",
retailer: retailer
)
product = history.data.first
next if product.nil? || product.offers.empty?
offer = product.offers.first
if offer.history.any?
prices = offer.history.map(&:price)
historical_comparison[retailer] = {
current: offer.price,
average: prices.sum.to_f / prices.length,
lowest: prices.min,
highest: prices.max,
volatility: prices.max - prices.min
}
end
end
puts "Retailer Historical Comparison:"
historical_comparison.each do |retailer, data|
puts "#{retailer.capitalize}:"
puts " Current: $#{data[:current]}"
puts " Average: $#{data[:average].round(2)}"
puts " Best ever: $#{data[:lowest]}"
puts " Worst: $#{data[:highest]}"
puts " Volatility: $#{data[:volatility].round(2)}"
puts
end# Monitor daily across all retailers
# Sends PUT /products/scheduled?ids=012345678901&schedule=daily
result = client.schedule_product_monitoring("012345678901", "daily")
# result.data is an Array<ScheduledProduct>: every product field plus `schedule`
result.data.each { |product| puts "#{product.title} (#{product.shopsavvy}): #{product.schedule}" }
# Monitor hourly at Amazon only (retailer is a domain)
amazon_only = client.schedule_product_monitoring(
"012345678901",
"hourly",
retailer: "amazon.com"
)
amazon_only.data.first.retailer # => "amazon.com"
puts "Credits used: #{amazon_only.credits_used}"
# Schedule multiple products
batch_result = client.schedule_product_monitoring_batch([
"012345678901",
"B08N5WRWNW"
], "daily")# Get all scheduled products
scheduled = client.get_scheduled_products
puts "Monitoring #{scheduled.data.length} products"
scheduled.data.each do |product|
retailer_info = product.retailer || "all retailers"
# schedule is nil when the stored interval has no Data API label (e.g. a 4h interval)
puts "#{product.title} (#{product.shopsavvy}): #{product.schedule || 'custom interval'} at #{retailer_info}"
end
# Remove from schedule (DELETE /products/scheduled?ids=012345678901)
# The response carries success, message and meta only (no data).
result = client.remove_product_from_schedule("012345678901")
puts result.message if result.success?
# Remove multiple products
client.remove_products_from_schedule(["012345678901", "B08N5WRWNW"])usage = client.get_usage
puts "Credits remaining: #{usage.data.credits_remaining}"
puts "Credits used: #{usage.data.credits_used}"
puts "Plan: #{usage.data.plan_name}"
puts "Usage: #{usage.data.credits_percentage_used}%"
puts "Billing period: #{usage.data.billing_period_start} to #{usage.data.billing_period_end}"begin
product = client.get_product_details("invalid-identifier")
rescue ShopsavvyDataApi::NotFoundError => e
puts "Product not found"
rescue ShopsavvyDataApi::AuthenticationError => e
puts "Invalid API key"
rescue ShopsavvyDataApi::RateLimitError => e
puts "Rate limit exceeded - slow down requests"
rescue ShopsavvyDataApi::ValidationError => e
puts "Invalid request parameters: #{e.message}"
rescue ShopsavvyDataApi::TimeoutError => e
puts "Request timed out"
rescue ShopsavvyDataApi::NetworkError => e
puts "Network error: #{e.message}"
rescue ShopsavvyDataApi::APIError => e
puts "API error: #{e.message}"
puts "Status code: #{e.status_code}" if e.status_code
endAll API methods return a consistent response format:
response = client.get_product_details("012345678901")
puts "Success: #{response.success?}"
puts "Data: #{response.data}"
puts "Credits used: #{response.credits_used}"
puts "Credits remaining: #{response.credits_remaining}"
# Access the actual data
product = response.data
puts "Product name: #{product.name}"# Offer convenience methods
offer = offers.data.first
puts "In stock: #{offer.in_stock?}"
puts "New condition: #{offer.new_condition?}"
# Scheduled product convenience methods
scheduled_product = scheduled.data.first
puts "Daily monitoring: #{scheduled_product.daily?}"
# Usage info convenience methods
usage_info = usage.data
puts "#{usage_info.credits_percentage_remaining}% credits remaining"Some endpoints support CSV format for easier data processing:
# Get product details in CSV format
product_csv = client.get_product_details("012345678901", format: "csv")
# Get offers in CSV format
offers_csv = client.get_current_offers("012345678901", format: "csv")
# Process with CSV library
require 'csv'
CSV.parse(offers_csv.data, headers: true) do |row|
puts "#{row['retailer']}: $#{row['price']}"
endAll model objects can be converted to hashes:
product = client.get_product_details("012345678901")
# Convert to hash
product_hash = product.data.to_h
puts product_hash[:name]
# Convert entire response to hash
response_hash = product.to_h
puts response_hash[:data][:name]# Gemfile
gem 'shopsavvy-sdk'
gem 'sidekiq' # For background jobs
# config/application.rb
config.shopsavvy_api_key = Rails.application.credentials.shopsavvy_api_key
# app/services/price_tracking_service.rb
class PriceTrackingService
def initialize
@client = ShopsavvyDataApi.new(
api_key: Rails.application.config.shopsavvy_api_key,
timeout: 60
)
end
def track_product(product_id, target_price)
# Schedule monitoring
@client.schedule_product_monitoring(product_id, 'daily')
# Create local tracking record
PriceAlert.create!(
product_identifier: product_id,
target_price: target_price,
status: 'active'
)
end
def check_price_alerts
PriceAlert.active.find_each do |alert|
CheckPriceAlertJob.perform_later(alert.id)
end
end
end
# app/jobs/check_price_alert_job.rb
class CheckPriceAlertJob < ApplicationJob
queue_as :default
def perform(alert_id)
alert = PriceAlert.find(alert_id)
client = ShopsavvyDataApi.new(api_key: Rails.application.config.shopsavvy_api_key)
offers = client.get_current_offers(alert.product_identifier)
best_offer = offers.data.min_by(&:price)
if best_offer && best_offer.price <= alert.target_price
# Send notification
PriceAlertMailer.target_reached(alert, best_offer).deliver_now
alert.update!(status: 'triggered', triggered_at: Time.current)
end
rescue ShopsavvyDataApi::Error => e
Rails.logger.error "ShopSavvy API error: #{e.message}"
# Optionally retry or alert administrators
end
end# app.rb
require 'sinatra'
require 'json'
require 'shopsavvy_data_api'
class PriceAPI < Sinatra::Base
configure do
set :shopsavvy_client, ShopsavvyDataApi.new(
api_key: ENV['SHOPSAVVY_API_KEY'],
timeout: 30
)
end
before do
content_type :json
end
get '/api/product/:identifier/price' do
identifier = params[:identifier]
begin
offers = settings.shopsavvy_client.get_current_offers(identifier)
{
success: true,
product_id: identifier,
offers: offers.data.map do |offer|
{
retailer: offer.retailer,
price: offer.price,
availability: offer.availability,
condition: offer.condition,
url: offer.url
}
end,
best_price: offers.data.min_by(&:price)&.price,
credits_remaining: offers.credits_remaining
}.to_json
rescue ShopsavvyDataApi::Error => e
status 400
{ success: false, error: e.message }.to_json
end
end
get '/api/product/:identifier/history' do
identifier = params[:identifier]
days = (params[:days] || 30).to_i
end_date = Date.today
start_date = end_date - days
begin
history = settings.shopsavvy_client.get_price_history(
identifier,
start_date.strftime('%Y-%m-%d'),
end_date.strftime('%Y-%m-%d')
)
{
success: true,
product_id: identifier,
period: "#{days} days",
data: history.data.map(&:to_h) # products, each with offers + history
}.to_json
rescue ShopsavvyDataApi::Error => e
status 400
{ success: false, error: e.message }.to_json
end
end
end# lib/price_monitor.rb
class PriceMonitor
include Sidekiq::Worker
sidekiq_options retry: 3, backtrace: true
def perform(product_ids)
client = ShopsavvyDataApi.new(api_key: ENV['SHOPSAVVY_API_KEY'])
product_ids.each do |product_id|
begin
# Get current prices
offers = client.get_current_offers(product_id)
next if offers.data.empty?
# Store in database or cache
best_price = offers.data.min_by(&:price).price
Redis.current.setex("price:#{product_id}", 3600, best_price)
# Check for alerts
check_price_alerts(product_id, best_price)
rescue ShopsavvyDataApi::RateLimitError => e
# Exponential backoff
self.class.perform_in(2 ** sidekiq_options['retry_count'], [product_id])
raise e
rescue ShopsavvyDataApi::Error => e
logger.error "API error for #{product_id}: #{e.message}"
end
end
end
private
def check_price_alerts(product_id, current_price)
# Implementation for checking and triggering alerts
end
end
# Schedule regular monitoring
PriceMonitor.perform_async(['012345678901', 'B08N5WRWNW'])# Comprehensive competitive analysis tool
class CompetitiveAnalyzer
def initialize(api_key)
@client = ShopsavvyDataApi.new(api_key: api_key)
end
def analyze_market(product_ids, competitors = %w[amazon walmart target bestbuy])
analysis = {}
product_ids.each do |product_id|
product_analysis = analyze_product_competition(product_id, competitors)
analysis[product_id] = product_analysis
end
generate_competitive_report(analysis)
end
private
def analyze_product_competition(product_id, competitors)
# Get product details
product = @client.get_product_details(product_id)
# Get current offers from all retailers
all_offers = @client.get_current_offers(product_id)
# Filter by target competitors
competitor_offers = all_offers.data.select do |offer|
competitors.include?(offer.retailer.downcase)
end
# Price analysis
prices = competitor_offers.map(&:price)
{
product_name: product.data.name,
brand: product.data.brand,
total_offers: all_offers.data.length,
competitor_offers: competitor_offers.length,
price_range: {
min: prices.min,
max: prices.max,
average: prices.sum.to_f / prices.length
},
market_position: calculate_market_position(competitor_offers),
availability_score: calculate_availability_score(competitor_offers)
}
end
def calculate_market_position(offers)
return 'No data' if offers.empty?
prices = offers.map(&:price).sort
median_price = prices[prices.length / 2]
case median_price
when 0..50 then 'Budget'
when 50..200 then 'Mid-range'
when 200..500 then 'Premium'
else 'Luxury'
end
end
def calculate_availability_score(offers)
return 0 if offers.empty?
in_stock_count = offers.count(&:in_stock?)
(in_stock_count.to_f / offers.length * 100).round(1)
end
end
# Usage
analyzer = CompetitiveAnalyzer.new(ENV['SHOPSAVVY_API_KEY'])
report = analyzer.analyze_market([
'012345678901', 'B08N5WRWNW', '045496590048'
])# Integration with inventory management system
class InventoryPriceManager
def initialize(api_key)
@client = ShopsavvyDataApi.new(api_key: api_key)
end
def update_competitive_pricing(inventory_items)
pricing_updates = []
inventory_items.each do |item|
next unless item.competitor_tracking_enabled?
begin
# Get current market prices
offers = @client.get_current_offers(item.barcode)
next if offers.data.empty?
# Calculate competitive price point
competitor_prices = offers.data.map(&:price)
market_analysis = analyze_market_prices(competitor_prices)
suggested_price = calculate_competitive_price(
item.cost_price,
market_analysis,
item.target_margin
)
pricing_updates << {
item_id: item.id,
current_price: item.selling_price,
suggested_price: suggested_price,
market_analysis: market_analysis,
reasoning: generate_pricing_reasoning(item, market_analysis, suggested_price)
}
rescue ShopsavvyDataApi::Error => e
Rails.logger.error "Pricing update failed for #{item.id}: #{e.message}"
next
end
end
pricing_updates
end
private
def analyze_market_prices(prices)
sorted_prices = prices.sort
{
min: sorted_prices.first,
max: sorted_prices.last,
median: sorted_prices[sorted_prices.length / 2],
average: prices.sum.to_f / prices.length,
percentile_25: sorted_prices[(sorted_prices.length * 0.25).round],
percentile_75: sorted_prices[(sorted_prices.length * 0.75).round]
}
end
def calculate_competitive_price(cost_price, market_analysis, target_margin)
min_price = cost_price * (1 + target_margin)
competitive_price = market_analysis[:percentile_25] * 0.95 # 5% under 25th percentile
[min_price, competitive_price].max.round(2)
end
end# Advanced market research and trend analysis
class MarketResearcher
def initialize(api_key)
@client = ShopsavvyDataApi.new(api_key: api_key)
end
def research_category_trends(product_categories, time_periods)
research_report = {}
product_categories.each do |category, product_list|
category_data = analyze_category_trends(product_list, time_periods)
research_report[category] = category_data
end
generate_market_intelligence_report(research_report)
end
def track_seasonal_patterns(product_id, months = 12)
patterns = {}
(0...months).each do |month_offset|
end_date = Date.today - (month_offset * 30)
start_date = end_date - 30
history = @client.get_price_history(
product_id,
start_date.strftime('%Y-%m-%d'),
end_date.strftime('%Y-%m-%d')
)
month_name = end_date.strftime('%B %Y')
patterns[month_name] = analyze_monthly_patterns(history.data)
end
identify_seasonal_trends(patterns)
end
private
def analyze_monthly_patterns(history_data)
return { average_price: 0, volatility: 0 } if history_data.empty?
all_prices = []
history_data.each do |product|
product.offers.each do |offer|
all_prices.concat(offer.history.map(&:price))
end
end
return { average_price: 0, volatility: 0 } if all_prices.empty?
average = all_prices.sum.to_f / all_prices.length
variance = all_prices.map { |price| (price - average) ** 2 }.sum / all_prices.length
{
average_price: average.round(2),
volatility: Math.sqrt(variance).round(2),
price_points: all_prices.length
}
end
end# Clone the repository
git clone https://github.com/shopsavvy/sdk-ruby.git
cd sdk-ruby
# Install dependencies
bundle install
# Set up environment variables
echo 'SHOPSAVVY_API_KEY=ss_test_your_test_key_here' > .env
# Run tests
bundle exec rspec
# Run linting
bundle exec rubocop
# Generate documentation
bundle exec yard doc# Create a test script
require 'shopsavvy_data_api'
# Use test API key (starts with ss_test_)
client = ShopsavvyDataApi.new(api_key: 'ss_test_your_test_key_here')
# Test basic functionality
begin
# Test product lookup
product = client.get_product_details('012345678901')
puts "β
Product lookup: #{product.data.name}"
# Test current offers
offers = client.get_current_offers('012345678901')
puts "β
Current offers: #{offers.data.length} found"
# Test usage info
usage = client.get_usage
puts "β
API usage: #{usage.data.credits_remaining} credits remaining"
puts "\nπ All tests passed! SDK is working correctly."
rescue ShopsavvyDataApi::Error => e
puts "β Test failed: #{e.message}"
end- ShopSavvy Data API Documentation - Complete API reference
- API Dashboard - Manage your API keys and usage
- GitHub Repository - Source code and issues
- RubyGems Page - Gem releases and stats
- Support - Get help from our team
We welcome contributions! Please see our Contributing Guide for details on:
- Reporting bugs
- Suggesting enhancements
- Submitting pull requests
- Development workflow
- Code standards
This project is licensed under the MIT License - see the LICENSE file for details.
ShopSavvy has been comparing prices since 2008, and the app has 40M+ downloads. The ShopSavvy Data API serves the same product and price data over REST: send a barcode (UPC/EAN/ISBN), ASIN, product URL, model number or MPN, and get back the product record, current offers from tens of thousands of retailers, and price history back to 2011 (dense from 2019).
- Lookup by any identifier: barcode, ASIN, URL, model number, MPN or ShopSavvy ID, up to 100 per request
- Current offers: price, availability, condition, seller and when each offer was last seen
- Price history: quality-filtered price points per retailer over any date range
- Scheduled refreshes: hourly, daily or weekly re-checks for the products you care about, at retailers that can be re-checked through an official retailer API (others update as ShopSavvy sees them)
- JSON or CSV responses, with credits used and remaining on every response
- Open-source SDKs in 10 languages: Python, TypeScript, Go, Rust, Java, Kotlin, Swift, PHP, Ruby and C#
Plans and the 14-day trial are at shopsavvy.com/data; full reference at shopsavvy.com/data/documentation.
Ready to get started? Sign up for your API key β’ Need help? Contact us