Official C# 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
// dotnet add package ShopSavvy.Sdk
// Use
using ShopSavvy.DataApi;
var client = new ShopSavvyDataApiClient("ss_live_your_api_key_here");
var product = await client.GetProductDetailsAsync("012345678901");
var offers = await client.GetOffersAsync("012345678901"); // one entry per product, each with Offers
var bestPrice = offers.Data[0].Offers.Where(o => o.Price.HasValue).MinBy(o => o.Price);
Console.WriteLine($"{product.Data.Title} - Best price: ${bestPrice.Price:F2} at {bestPrice.Retailer}");Install-Package ShopSavvy.Sdkdotnet add package ShopSavvy.Sdk<PackageReference Include="ShopSavvy.Sdk" Version="1.4.0" />- 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
// Basic configuration
using var client = new ShopSavvyDataApiClient("ss_live_your_api_key_here");
// Advanced configuration
var config = new ShopSavvyConfig
{
ApiKey = "ss_live_your_api_key_here",
BaseUrl = "https://api.shopsavvy.com/v1", // Custom base URL
Timeout = TimeSpan.FromSeconds(60), // Request timeout
RetryAttempts = 3, // Retry failed requests
RetryDelay = TimeSpan.FromSeconds(1), // Delay between retries
UserAgent = "MyApp/1.0.0" // Custom user agent
};
using var client = new ShopSavvyDataApiClient(config);
// Environment variable configuration
var apiKey = Environment.GetEnvironmentVariable("SHOPSAVVY_API_KEY");
using var client = new ShopSavvyDataApiClient(apiKey);// Look up by barcode, ASIN, URL, model number, or ShopSavvy ID
var product = await client.GetProductDetailsAsync("012345678901");
var amazonProduct = await client.GetProductDetailsAsync("B08N5WRWNW");
var urlProduct = await client.GetProductDetailsAsync("https://www.amazon.com/dp/B08N5WRWNW");
var modelProduct = await client.GetProductDetailsAsync("MQ023LL/A"); // iPhone model number
Console.WriteLine($"π¦ Product: {product.Data.Name}");
Console.WriteLine($"π·οΈ Brand: {product.Data.Brand}");
Console.WriteLine($"π Category: {product.Data.Category}");
Console.WriteLine($"π’ Product ID: {product.Data.ProductId}");
if (!string.IsNullOrEmpty(product.Data.Asin))
Console.WriteLine($"π¦ ASIN: {product.Data.Asin}");
if (!string.IsNullOrEmpty(product.Data.Model))
Console.WriteLine($"π§ Model: {product.Data.Model}");// Up to 100 identifiers per request
var identifiers = new[]
{
"012345678901", "B08N5WRWNW", "045496590048",
"https://www.bestbuy.com/site/product/123456",
"MQ023LL/A", "SM-S911U" // iPhone and Samsung model numbers
};
var products = await client.GetProductDetailsBatchAsync(identifiers);
foreach (var product in products.Data)
{
if (product != null)
{
Console.WriteLine($"β Found: {product.Name} by {product.Brand}");
if (product.Identifiers?.Any() == true)
{
Console.WriteLine($" Identifiers: {string.Join(", ", product.Identifiers.Select(i => $"{i.Key}:{i.Value}"))}");
}
}
}
// Handle potential errors in batch processing
for (int i = 0; i < products.Data.Length; i++)
{
if (products.Data[i] == null)
{
Console.WriteLine($"β Failed to find product: {identifiers[i]}");
}
}// GetOffersAsync returns one entry per matched product: every product field plus its Offers
var response = await client.GetOffersAsync("012345678901");
var product = response.Data[0];
var offers = product.Offers.Where(o => o.Price.HasValue).ToArray();
Console.WriteLine($"{product.Title}: {offers.Length} offers across retailers");
// Advanced price analysis
var sortedOffers = offers.OrderBy(o => o.Price).ToArray();
var cheapest = sortedOffers.First();
var mostExpensive = sortedOffers.Last();
Console.WriteLine($"π° Best price: {cheapest.Retailer} - ${cheapest.Price:F2}");
Console.WriteLine($"πΈ Highest price: {mostExpensive.Retailer} - ${mostExpensive.Price:F2}");
Console.WriteLine($"π Average price: ${offers.Average(o => o.Price):F2}");
Console.WriteLine($"π‘ Potential savings: ${mostExpensive.Price - cheapest.Price:F2}");
// Filter by availability ("in", "out", "limited", "pre-order", "coming-soon", "discontinued";
// null when unknown) and condition
var inStockOffers = offers.Where(o => o.Availability == "in").ToArray();
var newConditionOffers = offers.Where(o => o.Condition == "new").ToArray();
Console.WriteLine($"β
In-stock offers: {inStockOffers.Length}");
Console.WriteLine($"π New condition: {newConditionOffers.Length}");// Major retailers (retailer = domain name)
var retailers = new[] { "amazon.com", "walmart.com", "target.com", "bestbuy.com" };
var retailerPrices = new Dictionary<string, decimal>();
foreach (var retailer in retailers)
{
var response = await client.GetOffersAsync("012345678901", retailer);
var offers = response.Data.SelectMany(p => p.Offers).Where(o => o.Price.HasValue).ToArray();
if (offers.Any())
{
retailerPrices[retailer] = offers.Min(o => o.Price!.Value);
}
}
Console.WriteLine("Retailer price comparison:");
foreach (var (retailer, price) in retailerPrices.OrderBy(kvp => kvp.Value))
{
Console.WriteLine($" {char.ToUpper(retailer[0])}{retailer[1..]}: ${price:F2}");
}// Monitor multiple products simultaneously
var productList = new[]
{
"012345678901", "B08N5WRWNW", "045496590048",
"B07XJ8C8F5", "B09G9FPHY6"
};
var batchOffers = await client.GetOffersBatchAsync(productList);
// One entry per identifier that matched a product (unknown identifiers are omitted)
foreach (var product in batchOffers.Data)
{
var offers = product.Offers.Where(o => o.Price.HasValue).ToArray();
if (!offers.Any()) continue;
var bestOffer = offers.MinBy(o => o.Price);
Console.WriteLine($"{product.Title} ({product.Barcode}):");
Console.WriteLine($" Best price: {bestOffer.Retailer} - ${bestOffer.Price:F2}");
Console.WriteLine($" Total offers: {offers.Length}");
Console.WriteLine($" In stock: {offers.Count(o => o.Availability == "in")}");
Console.WriteLine();
}GetPriceHistoryAsync returns ApiResponse<ProductWithPriceHistory[]>: one entry per matched
product (every ProductDetails field, plus Offers). Each OfferWithHistory carries the offer's
current state at that retailer and a History array of PriceHistoryEntry points
(Timestamp, Price, Currency, Availability), newest first. Currency can be null and
Availability is null when it was not confirmed at that point. The window may span at most 366 days.
// Get 90 days of price history for detailed analysis
var endDate = DateTime.Today;
var startDate = endDate.AddDays(-90);
var history = await client.GetPriceHistoryAsync(
"012345678901",
startDate.ToString("yyyy-MM-dd"),
endDate.ToString("yyyy-MM-dd")
);
foreach (var product in history.Data)
{
Console.WriteLine($"π 90-Day Price Analysis: {product.Title}");
Console.WriteLine(new string('=', 50));
foreach (var offer in product.Offers.Where(o => o.History.Length > 0))
{
var prices = offer.History.Select(point => point.Price).ToArray();
var currentPrice = offer.Price ?? prices[0];
// Points are newest first: the first 7 are the most recent ones
var recentPrices = prices.Take(7).ToArray();
var olderPrices = prices.Skip(7).ToArray();
var trend = "π Insufficient data";
if (recentPrices.Any() && olderPrices.Any())
{
var recentAvg = recentPrices.Average();
var olderAvg = olderPrices.Average();
var changePct = Math.Round((recentAvg - olderAvg) / olderAvg * 100, 1);
trend = changePct switch
{
> 5 => $"π Rising (+{changePct}%)",
< -5 => $"π Falling ({changePct}%)",
_ => $"π Stable ({changePct:+0.0;-0.0;0.0}%)"
};
}
Console.WriteLine($"πͺ {offer.Retailer}");
Console.WriteLine($" Current: {currentPrice:F2} {offer.Currency}");
Console.WriteLine($" Average: {prices.Average():F2}");
Console.WriteLine($" Range: {prices.Min():F2} - {prices.Max():F2}");
Console.WriteLine($" Trend: {trend}");
Console.WriteLine($" Data points: {offer.History.Length} (latest {offer.History[0].Timestamp})");
Console.WriteLine();
}
}// Compare price history across major retailers (retailer = domain name)
var retailers = new[] { "amazon.com", "walmart.com", "target.com", "bestbuy.com" };
var historicalComparison = new Dictionary<string, dynamic>();
foreach (var retailer in retailers)
{
var history = await client.GetPriceHistoryAsync(
"012345678901",
"2024-01-01",
"2024-12-31",
retailer
);
var product = history.Data.FirstOrDefault();
var offer = product?.Offers.FirstOrDefault(o => o.History.Length > 0);
if (offer == null) continue;
var prices = offer.History.Select(point => point.Price).ToArray();
historicalComparison[retailer] = new
{
Current = offer.Price ?? prices[0],
Average = prices.Average(),
Lowest = prices.Min(),
Highest = prices.Max(),
Volatility = prices.Max() - prices.Min()
};
}
Console.WriteLine("Retailer Historical Comparison:");
foreach (var (retailer, data) in historicalComparison)
{
Console.WriteLine($"{char.ToUpper(retailer[0])}{retailer[1..]}:");
Console.WriteLine($" Current: ${data.Current:F2}");
Console.WriteLine($" Average: ${data.Average:F2}");
Console.WriteLine($" Best ever: ${data.Lowest:F2}");
Console.WriteLine($" Worst: ${data.Highest:F2}");
Console.WriteLine($" Volatility: ${data.Volatility:F2}");
Console.WriteLine();
}Schedule products to be refreshed hourly, daily or weekly (credit costs are in the API documentation).
The schedule and list calls return ScheduledProduct entries β every product field (Title,
Shopsavvy, Barcode, β¦) plus Schedule and Retailer. Retailer is null when a product is
watched across all retailers; on the list, Schedule is null for an interval with no Data API
label. Unscheduling is free and returns only Success, Message and Meta β no per-product data.
// Schedule one product daily at a single retailer
var scheduled = await client.ScheduleProductMonitoringAsync("012345678901", "daily", "bestbuy.com");
foreach (var p in scheduled.Data) // data is always a list; unknown identifiers are omitted
{
Console.WriteLine($"{p.Title} ({p.Shopsavvy}) -> {p.Schedule} at {p.Retailer ?? "all retailers"}");
}
Console.WriteLine($"Credits used: {scheduled.CreditsUsed()}");
// Schedule several products hourly across all retailers
var batch = await client.ScheduleProductMonitoringBatchAsync(new[] { "012345678901", "B08N5WRWNW" }, "hourly");
// List everything on the schedule
var list = await client.GetScheduledProductsAsync();
foreach (var p in list.Data)
{
Console.WriteLine($"{p.Title}: {p.Schedule ?? "custom interval"}{(p.Retailer != null ? $" @ {p.Retailer}" : "")}");
}
// Unschedule (single or batch) β no data, just a confirmation
var removed = await client.RemoveProductsFromScheduleAsync(new[] { "012345678901", "B08N5WRWNW" });
Console.WriteLine($"{removed.Success}: {removed.Message}");// Program.cs or Startup.cs
builder.Services.Configure<ShopSavvyConfig>(
builder.Configuration.GetSection("ShopSavvy"));
builder.Services.AddScoped<ShopSavvyDataApiClient>(provider =>
{
var config = provider.GetRequiredService<IOptions<ShopSavvyConfig>>().Value;
return new ShopSavvyDataApiClient(config);
});
// appsettings.json
{
"ShopSavvy": {
"ApiKey": "ss_live_your_api_key_here",
"Timeout": "00:01:00",
"RetryAttempts": 3
}
}
// PriceController.cs
[ApiController]
[Route("api/[controller]")]
public class PriceController : ControllerBase
{
private readonly ShopSavvyDataApiClient _client;
public PriceController(ShopSavvyDataApiClient client)
{
_client = client;
}
[HttpGet("{identifier}/offers")]
public async Task<IActionResult> GetOffers(string identifier)
{
try
{
var response = await _client.GetOffersAsync(identifier);
return Ok(new
{
success = true,
productId = identifier,
offers = response.Data.SelectMany(p => p.Offers).Select(o => new
{
retailer = o.Retailer,
price = o.Price,
availability = o.Availability,
condition = o.Condition,
url = o.Url
}),
bestPrice = response.Data.SelectMany(p => p.Offers).Min(o => o.Price),
creditsRemaining = response.CreditsRemaining()
});
}
catch (ShopSavvyApiException ex)
{
return BadRequest(new { success = false, error = ex.Message });
}
}
}// PriceMonitoringService.cs
public class PriceMonitoringService : BackgroundService
{
private readonly ShopSavvyDataApiClient _client;
private readonly ILogger<PriceMonitoringService> _logger;
public PriceMonitoringService(
ShopSavvyDataApiClient client,
ILogger<PriceMonitoringService> logger)
{
_client = client;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
await MonitorPrices();
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in price monitoring service");
}
}
}
private async Task MonitorPrices()
{
var products = new[] { "012345678901", "B08N5WRWNW" };
foreach (var productId in products)
{
try
{
var response = await _client.GetOffersAsync(productId);
var bestPrice = response.Data.SelectMany(p => p.Offers).Min(o => o.Price);
// Store in database or send alerts
_logger.LogInformation($"Product {productId}: Best price ${bestPrice:F2}");
}
catch (ShopSavvyRateLimitException)
{
// Implement exponential backoff
await Task.Delay(TimeSpan.FromMinutes(5));
}
catch (ShopSavvyApiException ex)
{
_logger.LogError(ex, $"API error for product {productId}");
}
}
}
}# Clone the repository
git clone https://github.com/shopsavvy/sdk-csharp.git
cd sdk-csharp
# Restore packages
dotnet restore
# Build the project
dotnet build
# Run tests
dotnet test tests/ShopSavvy.DataApi.Tests
# Create NuGet package
dotnet packusing ShopSavvy.DataApi;
// Use test API key (starts with ss_test_)
using var client = new ShopSavvyDataApiClient("ss_test_your_test_key_here");
try
{
// Test product lookup
var product = await client.GetProductDetailsAsync("012345678901");
Console.WriteLine($"β
Product lookup: {product.Data.Name}");
// Test current offers
var offers = await client.GetOffersAsync("012345678901");
Console.WriteLine($"β
Current offers: {offers.Data.Sum(p => p.Offers.Length)} found");
// Test usage info
var usage = await client.GetUsageAsync();
Console.WriteLine($"β
API usage: {usage.Data.CreditsRemaining} credits remaining");
Console.WriteLine("\nπ All tests passed! SDK is working correctly.");
}
catch (ShopSavvyApiException ex)
{
Console.WriteLine($"β Test failed: {ex.Message}");
}- ShopSavvy Data API Documentation - Complete API reference
- API Dashboard - Manage your API keys and usage
- GitHub Repository - Source code and issues
- NuGet Package - Package 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