smallpict / smallpict-php
Official PHP SDK for SmallPict Image Optimization API
Requires
- php: >=7.4
- ext-curl: *
- ext-hash: *
- ext-json: *
Requires (Dev)
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^9.5 || ^10.0
Suggests
- illuminate/support: Laravel integration: SmallPictServiceProvider and the SmallPict facade
- psr/http-client: Allows using modern PSR-18 HTTP clients like Guzzle or Symfony HttpClient
- psr/http-factory: Required when using PSR-18 HTTP clients
Provides
None
Conflicts
None
Replaces
None
README
Official PHP SDK for the SmallPict Image Optimization API โ high-performance next-gen image transcoding (AVIF, WebP), smart compression, Edge CDN delivery, and cache purging.
โก Features
- ๐ Broad PHP Compatibility: Supports PHP 7.4 through PHP 8.5 seamlessly.
- ๐ Zero-Dependency Default: Built-in high-performance native cURL client; PSR-18 compatible for modern stacks.
- ๐ฆ Optional Laravel Auto-Discovery: Instant integration via
SmallPictServiceProviderandSmallPictFacade. - ๐ก๏ธ Secure HMAC-SHA256 & Bearer Auth: Tamper-proof payload verification.
- โจ 4 Core Unified Methods:
optimize(),getQuota(),purgeCdn(), andvalidateKey(). - ๐ Resilience & Fault Tolerance: Automatic
Idempotency-KeyUUID injection, 30s timeouts, and exponential backoff with jitter on HTTP 429/5xx. - ๐ Zero-Leak Privacy: API keys and credentials are automatically redacted from error traces and exception logs.
๐ฅ Installation
composer require smallpict/smallpict-php
๐ Quick Start
1. Standalone PHP Example
<?php require_once __DIR__ . '/vendor/autoload.php'; use SmallPict\Client; use SmallPict\Models\OptimizeOptions; use SmallPict\Models\ImageFormat; $client = new Client( getenv('SMALLPICT_API_KEY'), getenv('SMALLPICT_SECRET_KEY') // Optional HMAC Secret Key ); $imageContent = file_get_contents('hero-banner.png'); $result = $client->optimize( $imageContent, new OptimizeOptions( ImageFormat::AVIF, // Target format: 'avif', 'webp', 'auto', etc. 80, // Quality: 1-100 1920 // Max width in pixels ) ); echo "Optimized CDN URL: " . $result->getUrl() . "\n"; echo "Saved: " . $result->getSavingsPercentage() . "% (" . $result->getBytesSaved() . " bytes)\n"; // Compression mode: CompressionMode::LOSSY (default), EXTREME, LOSSLESS or AUTO. // AUTO picks, per image, the lowest quality that keeps the visual score (SSIMULACRA 2) at or // above 70 and ignores quality (WP Pro/Business, Velocity, Momentum, sandbox: 5 per minute). // Where it cannot search (PNG or GIF output, animation, time limit) the job is lossy. // The server checks it against your plan and answers 403 mode_not_in_plan // (PermissionDeniedException, allowed modes in getDetails()['allowed_modes']). // The old 'lossless' => true option is deprecated; use 'mode' instead. $result = $client->optimize($imageContent, [ 'format' => ImageFormat::WEBP, 'mode' => \SmallPict\Models\CompressionMode::LOSSLESS, ]); // Extreme gives the smallest files on every plan. Leave quality out so the server // uses its default for the output format; a quality you set is kept or raised to the // format floor. Mode comparison: https://smallpict.app/en/docs/image-settings $result = $client->optimize($imageContent, [ 'mode' => \SmallPict\Models\CompressionMode::EXTREME, ]); // Optional upload folder (1-3 segments, e.g. "blog/2026"), sent as options.folder. // The server lower-cases it; read it back with $result->getFolder() or getJobStatus()->getFolder(). // A malformed folder throws ValidationException (server code 400 invalid_folder). $result = $client->optimize($imageContent, ['folder' => 'blog/2026']);
What optimize() does
optimize() returns the finished job, so getStatus() is always 'completed':
- It reads the source into memory (binary string, file path or stream resource) and sends
POST /v1/optimizewith the file name, type and exact byte count. The API queues the job and returns a pre-signed upload URL. - It PUTs the bytes to that URL at once, with only
Content-TypeandContent-Length(no API key or signature) and no retry. If the storage refuses the upload, it throwsSmallPictExceptionwith error codeupload_failedand the HTTP status. - It polls
getJobStatus()every poll interval (default 1 second) for up to the wait timeout (default 120 seconds), then returns the URL, format, bytes saved, mode and quality from the job status.
A failed job throws SmallPictException with error code job_failed, the server's message and getDetails()['job_id']. A job still running after the wait timeout throws TimeoutException naming the job id; the job keeps running on the server, so you can call getJobStatus($jobId) later.
The API does not fetch URLs: an http:// or https:// source throws ValidationException before any request. Download the image and pass its bytes.
Poll interval and wait timeout are the last two Config arguments, in seconds:
use SmallPict\Config; use SmallPict\Models\FallbackMode; $client = new Client(new Config( getenv('SMALLPICT_API_KEY'), getenv('SMALLPICT_SECRET_KEY'), null, // base URL 30.0, // request timeout (s) 3, // max retries FallbackMode::THROW, 1.0, // poll interval (s) 120.0 // wait timeout (s) ));
2. Laravel Integration (Laravel 10 / 11)
In config/services.php:
'smallpict' => [ 'api_key' => env('SMALLPICT_API_KEY'), 'secret_key' => env('SMALLPICT_SECRET_KEY'), 'fallback_mode' => 'passthrough', // 'throw' | 'passthrough' 'poll_interval' => 1.0, // seconds between job status polls in optimize() 'wait_timeout' => 120.0, // seconds optimize() waits for the job ],
In your Controller:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use SmallPict\Laravel\Facades\SmallPict; use SmallPict\Models\OptimizeOptions; use SmallPict\Models\ImageFormat; class MediaController extends Controller { public function store(Request $request) { $file = $request->file('avatar'); $result = SmallPict::optimize( $file->getRealPath(), new OptimizeOptions(ImageFormat::AUTO, 85) ); return response()->json([ 'cdn_url' => $result->getUrl(), 'format' => $result->getFormat(), 'savings' => "{$result->getSavingsPercentage()}%", ]); } }
3. WordPress Plugin / Theme Integration
use SmallPict\Client; use SmallPict\Models\OptimizeOptions; function my_custom_image_upload_handler($file_path) { $client = new Client(get_option('smallpict_api_key')); try { $result = $client->optimize($file_path, new OptimizeOptions()); return $result->getUrl(); } catch (\SmallPict\Exceptions\SmallPictException $e) { // Fallback safely to original image on error error_log("SmallPict optimization error: " . $e->getMessage()); return $file_path; } }
๐ Account Quota & Edge CDN Purge
use SmallPict\Client; $client = new Client(getenv('SMALLPICT_API_KEY')); // 1. Check real-time quota $quota = $client->getQuota(); echo "Plan: {$quota->getPlan()}, Quota used: {$quota->getQuotaPercentage()}%\n"; // 2. Invalidate CDN cache $client->purgeCdn(['https://cdn.smallpict.app/opt/hero-banner.avif']); echo "CDN cache invalidated successfully.\n"; // 3. Purge every file uploaded into one folder (or PurgeType::ALL for all your files). // Folder and all purges are limited per hour (10 and 2); one more throws // RateLimitException (purge_rate_limited), which the SDK does not retry. $client->purgeCdn([], \SmallPict\Models\PurgeType::FOLDER, 'blog/2026');
Capacity and upgrade links
getQuota()->getCapacity() returns a SmallPict\Models\Capacity for the product of your API key: getStorage() (limit_bytes, used_bytes, base_bytes, addon_blocks), getCdn() (limit_bytes, used_bytes, status), getBooster() (active, wp_quota_bytes, api_transforms, cdn_bytes, next_expiry), getLapse() (unpaid-account state), getPeriodEnd() and getManageUrl(). The groups are arrays with the API's keys. A member that does not apply to your plan is null; getCapacity() is null when the server could not read it or is too old to send it. Capacity is read-only here: storage add-ons and boosters are bought in the dashboard.
$capacity = $client->getQuota()->getCapacity(); if ($capacity !== null && $capacity->getStorage() !== null) { $s = $capacity->getStorage(); echo "Storage: {$s['used_bytes']} / {$s['limit_bytes']} bytes\n"; }
Every exception has getUpgradeUrl(): the dashboard page where the missing capacity is added (details.upgrade_url), or null. storage_quota_exceeded and account_read_only throw QuotaExceededException; mode_not_in_plan throws PermissionDeniedException with getAllowedModes(). The SDK never opens these links; show them to your user.
try { $client->optimize($imageContent); } catch (\SmallPict\Exceptions\QuotaExceededException $e) { echo $e->getMessage() . ' Add capacity: ' . ($e->getUpgradeUrl() ?? 'https://smallpict.app/dashboard/billing'); }
๐งช Testing
composer test
๐ Security & Schema Compatibility (2026)
mimeType&contentTypeDual Support:OptimizeOptionssupports bothgetMimeType()andgetContentType()methods. Outgoing optimization requests automatically include bothmime_typeandcontent_typein the payload for zero-disruption compatibility across all SmallPict API versions.
๐ License
MIT ยฉ SmallPict Engineering