Official PHP SDK for the ShopSavvy Data API. Access comprehensive product data, real-time pricing, and historical price trends across thousands of retailers and millions of products. Built for Laravel, Symfony, WordPress, and any PHP 8.0+ application.
// Install: composer require shopsavvy/shopsavvy-sdk-php
<?php
require_once 'vendor/autoload.php';
use ShopSavvy\SDK\ShopSavvyClient;
$client = new ShopSavvyClient('ss_live_your_api_key_here');
$product = $client->getProductDetails('012345678901');
$offers = $client->getCurrentOffers('012345678901');
$bestOffer = collect($offers->data)->sortBy('price')->first();
echo "{$product->data->name} - Best price: \${$bestOffer->price} at {$bestOffer->retailer}";| Feature | Free Tier | Pro | Enterprise |
|---|---|---|---|
| API Calls/Month | 1,000 | 100,000 | Unlimited |
| Product Details | β | β | β |
| Real-time Pricing | β | β | β |
| Price History | 30 days | 1 year | 5+ years |
| Bulk Operations | 10/batch | 100/batch | 1000/batch |
| Retailer Coverage | 50+ | 500+ | 1000+ |
| Rate Limiting | 60/hour | 1000/hour | Custom |
| Support | Community | Phone + Dedicated |
- PHP 8.0 or higher
- Composer 2.0+
- ext-json (typically included)
- Guzzle HTTP 7.0+ (auto-installed)
composer require shopsavvy/shopsavvy-sdk-php# Optional: Publish config (creates config/shopsavvy.php)
php artisan vendor:publish --provider="ShopSavvy\SDK\Laravel\ShopSavvyServiceProvider"
# Add to .env
SHOPSAVVY_API_KEY=ss_live_your_api_key_here# config/packages/shopsavvy.yaml
shopsavvy:
api_key: '%env(SHOPSAVVY_API_KEY)%'
timeout: 60- 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
<?php
require_once 'vendor/autoload.php';
use ShopSavvy\SDK\ShopSavvyClient;
use ShopSavvy\SDK\Exceptions\ShopSavvyException;
// Basic configuration
$client = new ShopSavvyClient('ss_live_your_api_key_here');
// Advanced configuration
$client = new ShopSavvyClient(
'ss_live_your_api_key_here',
'https://api.shopsavvy.com/v1', // Custom base URL
60.0, // Request timeout
3, // Retry attempts
'MyApp/1.0.0' // Custom user agent
);
// Environment variable configuration
$apiKey = $_ENV['SHOPSAVVY_API_KEY'] ?? getenv('SHOPSAVVY_API_KEY');
$client = new ShopSavvyClient($apiKey);// Look up by barcode, ASIN, URL, model number, or ShopSavvy ID
$product = $client->getProductDetails('012345678901');
$amazonProduct = $client->getProductDetails('B08N5WRWNW');
$urlProduct = $client->getProductDetails('https://www.amazon.com/dp/B08N5WRWNW');
$modelProduct = $client->getProductDetails('MQ023LL/A'); // iPhone model number
echo "π¦ Product: {$product->data->name}\n";
echo "π·οΈ Brand: " . ($product->data->brand ?? 'N/A') . "\n";
echo "π Category: " . ($product->data->category ?? 'N/A') . "\n";
echo "π’ Product ID: {$product->data->id}\n";
if ($product->data->asin) {
echo "π¦ ASIN: {$product->data->asin}\n";
}
if ($product->data->modelNumber) {
echo "π§ Model: {$product->data->modelNumber}\n";
}// Process up to 100 products at once (Pro plan)
$identifiers = [
'012345678901', 'B08N5WRWNW', '045496590048',
'https://www.bestbuy.com/site/product/123456',
'MQ023LL/A', 'SM-S911U' // iPhone and Samsung model numbers
];
$products = $client->getProductDetailsBatch($identifiers);
foreach ($products->data as $index => $product) {
if ($product !== null) {
echo "β Found: {$product->name} by " . ($product->brand ?? 'Unknown') . "\n";
} else {
echo "β Failed to find product: {$identifiers[$index]}\n";
}
}<?php
// app/Http/Controllers/PriceComparisonController.php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use ShopSavvy\SDK\ShopSavvyClient;
use ShopSavvy\SDK\Exceptions\ShopSavvyException;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class PriceComparisonController extends Controller
{
private ShopSavvyClient $client;
public function __construct()
{
$this->client = new ShopSavvyClient(config('shopsavvy.api_key'));
}
public function compare(Request $request): JsonResponse
{
$request->validate([
'identifier' => 'required|string'
]);
try {
$product = $this->client->getProductDetails($request->identifier);
$offers = $this->client->getCurrentOffers($request->identifier);
// Sort offers by price
$sortedOffers = collect($offers->data)
->filter(fn($offer) => $offer->price !== null)
->sortBy('price')
->values();
$bestOffer = $sortedOffers->first();
$worstOffer = $sortedOffers->last();
$averagePrice = $sortedOffers->avg('price');
// Calculate savings
$potentialSavings = $worstOffer ? ($worstOffer->price - $bestOffer->price) : 0;
// Filter by availability and condition
$inStockOffers = $sortedOffers->where('availability', 'in_stock');
$newConditionOffers = $sortedOffers->where('condition', 'new');
return response()->json([
'success' => true,
'product' => [
'name' => $product->data->name,
'brand' => $product->data->brand,
'category' => $product->data->category,
'id' => $product->data->id
],
'pricing' => [
'best_price' => $bestOffer->price ?? 0,
'worst_price' => $worstOffer->price ?? 0,
'average_price' => round($averagePrice, 2),
'potential_savings' => round($potentialSavings, 2),
'currency' => $bestOffer->currency ?? 'USD'
],
'statistics' => [
'total_offers' => $sortedOffers->count(),
'in_stock_offers' => $inStockOffers->count(),
'new_condition_offers' => $newConditionOffers->count()
],
'offers' => $sortedOffers->map(function ($offer) use ($bestOffer) {
return [
'retailer' => $offer->retailer,
'price' => $offer->price,
'availability' => $offer->availability,
'condition' => $offer->condition,
'shipping_cost' => $offer->shippingCost,
'url' => $offer->url,
'is_best_price' => $offer->price === $bestOffer->price,
'total_cost' => ($offer->price ?? 0) + ($offer->shippingCost ?? 0)
];
})->toArray(),
'credits_remaining' => $offers->creditsRemaining
]);
} catch (ShopSavvyException $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 400);
}
}
}function analyzeOffers(ShopSavvyClient $client, string $identifier): array
{
try {
$response = $client->getCurrentOffers($identifier);
$offers = $response->data;
echo "Found " . count($offers) . " offers across retailers\n";
// Filter valid prices and sort
$validOffers = array_filter($offers, fn($offer) => $offer->price !== null);
usort($validOffers, fn($a, $b) => $a->price <=> $b->price);
if (empty($validOffers)) {
return ['error' => 'No valid offers found'];
}
$cheapest = $validOffers[0];
$mostExpensive = end($validOffers);
$prices = array_column($validOffers, 'price');
$average = array_sum($prices) / count($prices);
echo "π° Best price: {$cheapest->retailer} - $" . number_format($cheapest->price, 2) . "\n";
echo "πΈ Highest price: {$mostExpensive->retailer} - $" . number_format($mostExpensive->price, 2) . "\n";
echo "π Average price: $" . number_format($average, 2) . "\n";
echo "π‘ Potential savings: $" . number_format($mostExpensive->price - $cheapest->price, 2) . "\n";
// Additional analysis
$inStockOffers = array_filter($offers, fn($offer) => $offer->availability === 'in_stock');
$newConditionOffers = array_filter($offers, fn($offer) => $offer->condition === 'new');
echo "β
In-stock offers: " . count($inStockOffers) . "\n";
echo "π New condition: " . count($newConditionOffers) . "\n";
return [
'cheapest' => $cheapest,
'most_expensive' => $mostExpensive,
'average' => $average,
'savings' => $mostExpensive->price - $cheapest->price,
'total_offers' => count($offers),
'in_stock_count' => count($inStockOffers),
'new_condition_count' => count($newConditionOffers)
];
} catch (ShopSavvyException $e) {
echo "Error analyzing offers: " . $e->getMessage() . "\n";
return ['error' => $e->getMessage()];
}
}// Compare prices across major retailers
$retailers = ['amazon', 'walmart', 'target', 'bestbuy'];
$retailerPrices = [];
foreach ($retailers as $retailer) {
try {
$offers = $client->getCurrentOffers('012345678901', $retailer);
if (!empty($offers->data)) {
// Find best offer for this retailer
$bestPrice = min(array_column($offers->data, 'price'));
$retailerPrices[$retailer] = $bestPrice;
}
} catch (ShopSavvyException $e) {
echo "Error fetching {$retailer} prices: " . $e->getMessage() . "\n";
continue;
}
}
// Sort and display results
asort($retailerPrices);
echo "Retailer price comparison:\n";
foreach ($retailerPrices as $retailer => $price) {
echo " " . ucfirst($retailer) . ": $" . number_format($price, 2) . "\n";
}<?php
// app/Services/PriceMonitoringService.php
namespace App\Services;
use App\Models\ProductWatch;
use App\Models\PriceAlert;
use App\Notifications\PriceDropNotification;
use ShopSavvy\SDK\ShopSavvyClient;
use ShopSavvy\SDK\Exceptions\ShopSavvyException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
class PriceMonitoringService
{
private ShopSavvyClient $client;
public function __construct()
{
$this->client = new ShopSavvyClient(config('shopsavvy.api_key'));
}
public function addProductWatch(string $identifier, float $targetPrice, int $userId): ProductWatch
{
// Get product details first
$product = $this->client->getProductDetails($identifier);
// Schedule monitoring with API
$this->client->scheduleProductMonitoring($identifier, 'daily');
// Store in local database
return ProductWatch::create([
'user_id' => $userId,
'product_identifier' => $identifier,
'product_name' => $product->data->name,
'product_brand' => $product->data->brand,
'target_price' => $targetPrice,
'is_active' => true
]);
}
public function checkPriceDrops(): void
{
$activeWatches = ProductWatch::where('is_active', true)->get();
foreach ($activeWatches as $watch) {
try {
// Check cache first to avoid rate limiting
$cacheKey = "price_check_{$watch->product_identifier}";
if (Cache::has($cacheKey)) {
continue;
}
$offers = $this->client->getCurrentOffers($watch->product_identifier);
$bestOffer = collect($offers->data)
->filter(fn($offer) => $offer->price !== null)
->sortBy('price')
->first();
if ($bestOffer && $bestOffer->price <= $watch->target_price) {
// Create price alert
$alert = PriceAlert::create([
'product_watch_id' => $watch->id,
'price' => $bestOffer->price,
'retailer' => $bestOffer->retailer,
'url' => $bestOffer->url,
'triggered_at' => now()
]);
// Send notification to user
$watch->user->notify(new PriceDropNotification($alert));
Log::info("Price alert triggered", [
'product' => $watch->product_name,
'target' => $watch->target_price,
'actual' => $bestOffer->price,
'retailer' => $bestOffer->retailer
]);
}
// Cache for 1 hour to prevent excessive API calls
Cache::put($cacheKey, true, now()->addHour());
} catch (ShopSavvyException $e) {
Log::error("Price check failed", [
'product_identifier' => $watch->product_identifier,
'error' => $e->getMessage()
]);
}
// Rate limiting - sleep between requests
usleep(500000); // 0.5 seconds
}
}
public function getMarketAnalysis(string $identifier): array
{
$offers = $this->client->getCurrentOffers($identifier);
$history = $this->client->getPriceHistory(
$identifier,
now()->subDays(30)->format('Y-m-d'),
now()->format('Y-m-d')
);
return [
'current_analysis' => $this->analyzePrices($offers->data),
'historical_trends' => $this->analyzeHistory($history->data),
'recommendations' => $this->generateRecommendations($offers->data, $history->data)
];
}
private function analyzePrices(array $offers): array
{
$validOffers = array_filter($offers, fn($offer) => $offer->price !== null);
if (empty($validOffers)) {
return ['error' => 'No valid offers found'];
}
$prices = array_column($validOffers, 'price');
sort($prices);
return [
'min_price' => min($prices),
'max_price' => max($prices),
'avg_price' => array_sum($prices) / count($prices),
'median_price' => $this->median($prices),
'price_range' => max($prices) - min($prices),
'total_retailers' => count($validOffers),
'in_stock_count' => count(array_filter($validOffers, fn($o) => $o->availability === 'in_stock'))
];
}
private function median(array $numbers): float
{
$count = count($numbers);
$middle = floor(($count - 1) / 2);
if ($count % 2) {
return $numbers[$middle];
} else {
return ($numbers[$middle] + $numbers[$middle + 1]) / 2;
}
}
}<?php
// wp-content/plugins/shopsavvy-price-tracker/shopsavvy-price-tracker.php
/**
* Plugin Name: ShopSavvy Price Tracker
* Description: Track product prices and get alerts when prices drop
* Version: 1.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
require_once plugin_dir_path(__FILE__) . 'vendor/autoload.php';
use ShopSavvy\SDK\ShopSavvyClient;
use ShopSavvy\SDK\Exceptions\ShopSavvyException;
class ShopSavvyPriceTracker
{
private ShopSavvyClient $client;
public function __construct()
{
$this->client = new ShopSavvyClient(get_option('shopsavvy_api_key'));
add_action('init', [$this, 'init']);
add_action('wp_ajax_shopsavvy_search_product', [$this, 'ajaxSearchProduct']);
add_action('wp_ajax_shopsavvy_get_offers', [$this, 'ajaxGetOffers']);
add_shortcode('shopsavvy_price_comparison', [$this, 'priceComparisonShortcode']);
}
public function init(): void
{
// Register custom post type for tracked products
register_post_type('tracked_product', [
'public' => true,
'label' => 'Tracked Products',
'supports' => ['title', 'custom-fields'],
'menu_icon' => 'dashicons-chart-line'
]);
}
public function ajaxSearchProduct(): void
{
check_ajax_referer('shopsavvy_nonce');
$identifier = sanitize_text_field($_POST['identifier'] ?? '');
if (empty($identifier)) {
wp_die(json_encode(['error' => 'Product identifier required']));
}
try {
$product = $this->client->getProductDetails($identifier);
$offers = $this->client->getCurrentOffers($identifier);
$bestOffer = null;
$minPrice = PHP_FLOAT_MAX;
foreach ($offers->data as $offer) {
if ($offer->price && $offer->price < $minPrice) {
$minPrice = $offer->price;
$bestOffer = $offer;
}
}
wp_send_json_success([
'product' => [
'id' => $product->data->id,
'name' => $product->data->name,
'brand' => $product->data->brand,
'category' => $product->data->category,
'image' => $product->data->images[0] ?? null
],
'best_offer' => $bestOffer ? [
'retailer' => $bestOffer->retailer,
'price' => $bestOffer->price,
'url' => $bestOffer->url,
'availability' => $bestOffer->availability
] : null,
'total_offers' => count($offers->data)
]);
} catch (ShopSavvyException $e) {
wp_send_json_error(['message' => $e->getMessage()]);
}
}
public function priceComparisonShortcode(array $atts): string
{
$atts = shortcode_atts([
'identifier' => '',
'show_chart' => 'true'
], $atts);
if (empty($atts['identifier'])) {
return '<p>Product identifier required</p>';
}
try {
$product = $this->client->getProductDetails($atts['identifier']);
$offers = $this->client->getCurrentOffers($atts['identifier']);
ob_start();
?>
<div class="shopsavvy-price-comparison" data-identifier="<?= esc_attr($atts['identifier']) ?>">
<h3><?= esc_html($product->data->name) ?></h3>
<?php if ($product->data->brand): ?>
<p class="brand">Brand: <?= esc_html($product->data->brand) ?></p>
<?php endif; ?>
<div class="offers-grid">
<?php foreach ($offers->data as $offer): ?>
<?php if ($offer->price): ?>
<div class="offer-card">
<h4><?= esc_html($offer->retailer) ?></h4>
<div class="price">$<?= number_format($offer->price, 2) ?></div>
<?php if ($offer->availability): ?>
<div class="availability <?= esc_attr($offer->availability) ?>">
<?= esc_html(ucfirst(str_replace('_', ' ', $offer->availability))) ?>
</div>
<?php endif; ?>
<?php if ($offer->url): ?>
<a href="<?= esc_url($offer->url) ?>" target="_blank" class="view-offer">
View Offer
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<style>
.shopsavvy-price-comparison {
margin: 20px 0;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.offers-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 20px;
}
.offer-card {
border: 1px solid #eee;
padding: 15px;
border-radius: 5px;
text-align: center;
}
.offer-card .price {
font-size: 24px;
font-weight: bold;
color: #2c5aa0;
margin: 10px 0;
}
.availability.in_stock {
color: #46b450;
font-weight: bold;
}
.availability.out_of_stock {
color: #dc3232;
}
.view-offer {
display: inline-block;
margin-top: 10px;
padding: 8px 16px;
background: #2c5aa0;
color: white;
text-decoration: none;
border-radius: 4px;
}
</style>
<?php
return ob_get_clean();
} catch (ShopSavvyException $e) {
return '<p>Error loading product data: ' . esc_html($e->getMessage()) . '</p>';
}
}
}
new ShopSavvyPriceTracker();<?php
// src/Service/ShopSavvyService.php
namespace App\Service;
use ShopSavvy\SDK\ShopSavvyClient;
use ShopSavvy\SDK\Exceptions\ShopSavvyException;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Psr\Log\LoggerInterface;
class ShopSavvyService
{
private ShopSavvyClient $client;
public function __construct(
#[Autowire('%env(SHOPSAVVY_API_KEY)%')] string $apiKey,
private LoggerInterface $logger
) {
$this->client = new ShopSavvyClient($apiKey);
}
public function compareProductPrices(string $identifier): array
{
try {
$product = $this->client->getProductDetails($identifier);
$offers = $this->client->getCurrentOffers($identifier);
$validOffers = array_filter($offers->data, fn($offer) => $offer->price !== null);
usort($validOffers, fn($a, $b) => $a->price <=> $b->price);
return [
'product' => $product->data,
'offers' => $validOffers,
'best_price' => $validOffers[0]->price ?? null,
'price_range' => [
'min' => $validOffers[0]->price ?? null,
'max' => end($validOffers)->price ?? null
],
'statistics' => [
'total_offers' => count($validOffers),
'average_price' => $validOffers ? array_sum(array_column($validOffers, 'price')) / count($validOffers) : 0
]
];
} catch (ShopSavvyException $e) {
$this->logger->error('ShopSavvy API error', [
'identifier' => $identifier,
'error' => $e->getMessage()
]);
throw $e;
}
}
}The SDK provides comprehensive exception handling with specific exception types:
use ShopSavvy\SDK\Exceptions\{
ShopSavvyAuthenticationException,
ShopSavvyNotFoundException,
ShopSavvyValidationException,
ShopSavvyRateLimitException,
ShopSavvyNetworkException,
ShopSavvyException
};
try {
$response = $client->getProductDetails('012345678901');
// Handle success
} catch (ShopSavvyAuthenticationException $e) {
// Invalid API key or authentication failure
error_log("π Authentication failed: " . $e->getMessage());
} catch (ShopSavvyNotFoundException $e) {
// Product not found in database
error_log("β Product not found: " . $e->getMessage());
} catch (ShopSavvyValidationException $e) {
// Invalid parameters provided
error_log("β οΈ Invalid parameters: " . $e->getMessage());
} catch (ShopSavvyRateLimitException $e) {
// Rate limit exceeded
error_log("π¦ Rate limit exceeded: " . $e->getMessage());
// Get retry delay if available
$retryAfter = $e->getRetryAfter(); // seconds
if ($retryAfter) {
error_log("Retry after: {$retryAfter} seconds");
}
} catch (ShopSavvyNetworkException $e) {
// Network connectivity issues
error_log("π Network error: " . $e->getMessage());
} catch (ShopSavvyException $e) {
// Generic API error
error_log("β API error: " . $e->getMessage());
} catch (Exception $e) {
// Unexpected errors
error_log("π₯ Unexpected error: " . $e->getMessage());
}function retryWithBackoff(callable $operation, int $maxAttempts = 3): mixed
{
$attempt = 1;
$delay = 1; // Initial delay in seconds
while ($attempt <= $maxAttempts) {
try {
return $operation();
} catch (ShopSavvyRateLimitException $e) {
if ($attempt === $maxAttempts) {
throw $e;
}
// Use server-specified retry delay if available
$retryDelay = $e->getRetryAfter() ?? $delay;
error_log("Rate limited, retrying in {$retryDelay} seconds...");
sleep($retryDelay);
} catch (ShopSavvyNetworkException $e) {
if ($attempt === $maxAttempts) {
throw $e;
}
error_log("Network error, retrying in {$delay} seconds...");
sleep($delay);
}
$attempt++;
$delay *= 2; // Exponential backoff
}
}
// Usage example
$product = retryWithBackoff(function() use ($client) {
return $client->getProductDetails('012345678901');
});# Clone the repository
git clone https://github.com/shopsavvy/sdk-php.git
cd sdk-php
# Install dependencies
composer install
# Run tests
composer test
# Run static analysis
composer phpstan
# Run code formatting
composer format<?php
// tests/IntegrationTest.php
require_once 'vendor/autoload.php';
use ShopSavvy\SDK\ShopSavvyClient;
use ShopSavvy\SDK\Exceptions\ShopSavvyException;
class SDKTester
{
private ShopSavvyClient $client;
public function __construct()
{
// Use test API key (starts with ss_test_)
$this->client = new ShopSavvyClient('ss_test_your_test_key_here');
}
public function runTests(): void
{
try {
// Test product lookup
$product = $this->client->getProductDetails('012345678901');
echo "β
Product lookup: {$product->data->name}\n";
// Test current offers
$offers = $this->client->getCurrentOffers('012345678901');
echo "β
Current offers: " . count($offers->data) . " found\n";
// Test usage info
$usage = $this->client->getUsage();
echo "β
API usage: " . ($usage->data->creditsRemaining ?? 0) . " credits remaining\n";
echo "\nπ All tests passed! SDK is working correctly.\n";
} catch (ShopSavvyException $e) {
echo "β Test failed: " . $e->getMessage() . "\n";
}
}
}
$tester = new SDKTester();
$tester->runTests();All data is returned as strongly-typed PHP 8.0+ objects with full type hints:
class ProductDetails
{
public readonly string $id;
public readonly string $name;
public readonly ?string $description;
public readonly ?string $brand;
public readonly ?string $category;
public readonly ?string $upc;
public readonly ?string $asin;
public readonly ?string $modelNumber;
public readonly array $images;
public readonly array $specifications;
public readonly ?string $createdAt;
public readonly ?string $updatedAt;
// Helper methods
public function hasImages(): bool
{
return !empty($this->images);
}
public function getDisplayName(): string
{
return trim($this->brand . ' ' . $this->name);
}
public function getMainImage(): ?string
{
return $this->images[0] ?? null;
}
}class Offer
{
public readonly string $retailer;
public readonly ?float $price;
public readonly string $currency;
public readonly ?string $availability;
public readonly ?string $condition;
public readonly ?float $shippingCost;
public readonly ?string $url;
public readonly ?string $lastUpdated;
// Helper methods
public function isInStock(): bool
{
return $this->availability === 'in_stock';
}
public function isNewCondition(): bool
{
return $this->condition === 'new';
}
public function getTotalCost(): float
{
return ($this->price ?? 0) + ($this->shippingCost ?? 0);
}
public function getFormattedPrice(): string
{
return '$' . number_format($this->price ?? 0, 2);
}
}class UsageInfo
{
public readonly ?int $creditsUsed;
public readonly ?int $creditsRemaining;
public readonly ?int $creditsLimit;
public readonly ?string $resetDate;
public readonly ?string $currentPeriodStart;
public readonly ?string $currentPeriodEnd;
// Helper methods
public function getUsagePercentage(): float
{
$used = $this->creditsUsed ?? 0;
$limit = $this->creditsLimit ?? 1;
return ($used / $limit) * 100;
}
public function isNearLimit(float $threshold = 80.0): bool
{
return $this->getUsagePercentage() > $threshold;
}
public function getDaysUntilReset(): ?int
{
if (!$this->resetDate) {
return null;
}
$resetTime = strtotime($this->resetDate);
$currentTime = time();
return max(0, ceil(($resetTime - $currentTime) / 86400));
}
}- ShopSavvy Data API Documentation - Complete API reference
- API Dashboard - Manage your API keys and usage
- GitHub Repository - Source code and issues
- Packagist - Package releases and stats
- PHP Documentation - PHP language reference
- Laravel Documentation - Laravel framework guide
- Symfony Documentation - Symfony framework guide
- Support - Get help from our team
We welcome contributions! Please see our Contributing Guide for details on:
- Reporting bugs and feature requests
- Setting up development environment
- Submitting pull requests
- Code standards and testing
- PHP best practices and PSR compliance
This project is licensed under the MIT License - see the LICENSE file for details.
ShopSavvy is the world's first mobile shopping app, helping consumers find the best deals since 2008. With over 40 million downloads and millions of active users, ShopSavvy has saved consumers billions of dollars.
- π E-commerce platforms with competitive intelligence
- π Market research with real-time pricing data
- πͺ Retailers with inventory and pricing optimization
- π± Mobile apps with product lookup and price comparison
- π€ Business intelligence with automated price monitoring
- β Trusted by millions - Proven at scale since 2008
- β Comprehensive coverage - 1000+ retailers, millions of products
- β Real-time accuracy - Fresh data updated continuously
- β Developer-friendly - Easy integration, great documentation
- β Reliable infrastructure - 99.9% uptime, enterprise-grade
- β Flexible pricing - Plans for every use case and budget
- π Framework agnostic - Works with Laravel, Symfony, WordPress, and vanilla PHP
- π Modern PHP 8.0+ - Type hints, readonly properties, and latest features
- π‘οΈ Robust error handling - Comprehensive exception hierarchy
- β‘ Production ready - Built-in retry logic, rate limiting, and caching support
- π§ͺ Well tested - Comprehensive test suite with PHPUnit and PHPStan
Ready to get started? Sign up for your API key β’ Need help? Contact us