tims / laravel-amazon-spapi
Laravel integration for Amazon's official Selling Partner API PHP SDK
Requires
- php: ^8.3
- amzn-spapi/sdk: ^1.0
- guzzlehttp/guzzle: ^7.9
- illuminate/cache: ^10.0|^11.0|^12.0
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/events: ^10.0|^11.0|^12.0
- illuminate/queue: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- laravel/pint: ^1.18
- mockery/mockery: ^1.6
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
Suggests
- illuminate/bus: Required to dispatch report/feed queue jobs (^10|^11|^12).
- mockery/mockery: Required to use Tims\AmazonSpApi\Testing\SpApiFake.
README
Laravel integration for Amazon’s official Selling Partner API PHP SDK (amzn-spapi/sdk).
Why this package?
amzn-spapi/sdk is the API client. This package adds the Laravel layer around it:
- Config and environment-based LWA credentials
- Single-seller and multi-seller credential storage
- Access-token and Restricted Data Token (RDT) caching
- Automatic RDT attachment for PII endpoints
- OAuth helpers for Seller Central authorization
- Grantless client helpers (Notifications, client-secret rotation)
- Feed and report document upload/download
- HTTP retries for 429 and transient 5xx responses
- Queue jobs for report/feed create → poll → download
- Test fakes via
SpApiFake
You continue to call official SpApi\Api\... classes; this package configures and supports them in Laravel.
Requirements
- PHP 8.3+
- Laravel 10, 11, or 12
Installation
composer require tims/laravel-amazon-spapi
Publish the config:
php artisan vendor:publish --tag=amazon-spapi-config
Configuration
Add these to your .env:
AMAZON_SPAPI_INSTALLATION_TYPE=single AMAZON_SPAPI_LWA_CLIENT_ID= AMAZON_SPAPI_LWA_CLIENT_SECRET= AMAZON_SPAPI_LWA_REFRESH_TOKEN= # NA, EU, or FE AMAZON_SPAPI_ENDPOINT_REGION=NA # Optional AMAZON_SPAPI_SANDBOX=false AMAZON_SPAPI_APPLICATION_ID= AMAZON_SPAPI_REDIRECT_URI=https://your-app.test/amazon/callback # Automatic RDT (enabled by default) AMAZON_SPAPI_RDT_AUTO=true AMAZON_SPAPI_RDT_DATA_ELEMENTS=buyerInfo,shippingAddress # Retries for 429 / 5xx (enabled by default) AMAZON_SPAPI_RETRY_ENABLED=true AMAZON_SPAPI_RETRY_MAX_ATTEMPTS=3 AMAZON_SPAPI_RETRY_BASE_DELAY_MS=500
Single-seller mode
Type-hint SpApiManager or use the facade to build official SDK API clients:
use SpApi\Api\sellers\v1\SellersApi; use Tims\AmazonSpApi\Facades\AmazonSpApi; use Tims\AmazonSpApi\SpApiManager; public function index(SpApiManager $spApi) { /** @var SellersApi $api */ $api = $spApi->make(SellersApi::class); $result = $api->getMarketplaceParticipations(); return response()->json($result); } // Or via facade: $api = AmazonSpApi::make(SellersApi::class);
In single-seller mode you can also resolve \SpApi\Configuration from the container.
Multi-seller mode
- Set
AMAZON_SPAPI_INSTALLATION_TYPE=multi(orinstallation_typein config). - Publish and run migrations:
php artisan vendor:publish --tag=amazon-spapi-multi-seller php artisan migrate
- Store sellers and credentials:
use Tims\AmazonSpApi\Models\Credentials; use Tims\AmazonSpApi\Models\Seller; $seller = Seller::create(['name' => 'My Seller']); $credentials = Credentials::create([ 'seller_id' => $seller->id, 'selling_partner_id' => 'A********', 'region' => 'NA', // Optional when using shared app credentials from .env 'client_id' => null, 'client_secret' => null, 'refresh_token' => 'Atzr|...', ]);
- Build API clients from a credentials row:
use SpApi\Api\sellers\v1\SellersApi; $api = $credentials->make(SellersApi::class); $result = $api->getMarketplaceParticipations();
client_id / client_secret fall back to the single-seller LWA env values when left null (shared SP-API application). Refresh tokens and client secrets are stored encrypted.
OAuth
Build Seller Central authorize URLs and exchange authorization codes for refresh tokens:
use Tims\AmazonSpApi\Enums\Marketplace; use Tims\AmazonSpApi\OAuth; $oauth = OAuth::fromConfig(); $authorizeUrl = $oauth->getAuthorizationUri( marketplace: Marketplace::US, state: $state, draftApp: true, ); // After Amazon redirects back with spapi_oauth_code: $refreshToken = $oauth->getRefreshToken($request->query('spapi_oauth_code'));
Restricted Data Tokens (RDT)
Configure dataElements once. With automatic RDT enabled, restricted calls do not need a manual restrictedDataToken argument:
use SpApi\Api\orders\v0\OrdersV0Api; use Tims\AmazonSpApi\Facades\AmazonSpApi; $ordersApi = AmazonSpApi::make(OrdersV0Api::class, options: [ 'data_elements' => ['buyerInfo', 'shippingAddress'], ]); $result = $ordersApi->getOrders( ['ATVPDKIKX0DER'], '2024-01-01T00:00:00Z', );
Or use .env / config defaults:
AMAZON_SPAPI_RDT_AUTO=true AMAZON_SPAPI_RDT_DATA_ELEMENTS=buyerInfo,shippingAddress # AMAZON_SPAPI_RDT_TARGET_APPLICATION= AMAZON_SPAPI_RDT_SKIP_IN_SANDBOX=true
$ordersApi = AmazonSpApi::make(OrdersV0Api::class); $ordersApi->getOrder($orderId);
Disable automatic RDT when you want to pass tokens yourself:
AmazonSpApi::make(OrdersV0Api::class, options: ['auto_rdt' => false]);
Manual RDT
$rdt = AmazonSpApi::restrictedDataToken( path: '/orders/v0/orders', dataElements: ['buyerInfo', 'shippingAddress'], ); $ordersApi = AmazonSpApi::make(OrdersV0Api::class, options: ['auto_rdt' => false]); $result = $ordersApi->getOrders( ['ATVPDKIKX0DER'], '2024-01-01T00:00:00Z', restrictedDataToken: $rdt, );
AmazonSpApi::isRestrictedOperation('OrdersV0Api-getOrder') uses the official SDK restricted-operations list.
In sandbox mode, automatic RDT is skipped (skip_in_sandbox). Restricted sandbox calls work without an RDT.
Grantless operations
Some Notifications and Application Management calls do not use a seller refresh token. They use app client id/secret plus an LWA scope:
use SpApi\Api\notifications\v1\NotificationsApi; use Tims\AmazonSpApi\Enums\GrantlessScope; use Tims\AmazonSpApi\Facades\AmazonSpApi; $api = AmazonSpApi::makeGrantless( NotificationsApi::class, GrantlessScope::Notifications, // or ClientCredentialRotation ); $destinations = $api->getDestinations();
Feed / report documents
After createFeedDocument / getReportDocument / getFeedDocument, use Document to upload or download (including GZIP):
use SpApi\Model\feeds\v2021_06_30\CreateFeedDocumentSpecification; use Tims\AmazonSpApi\Document; // Report download $reportDoc = $reportsApi->getReportDocument($documentId); $contents = Document::fromReportDocument($reportDoc)->download(); $rows = Document::fromReportDocument($reportDoc)->downloadParsed('tsv'); // Feed upload $contentType = Document::contentTypeForFeed('POST_PRODUCT_PRICING_DATA'); $created = $feedsApi->createFeedDocument(new CreateFeedDocumentSpecification(['content_type' => $contentType])); $doc = Document::fromCreateFeedDocumentResponse($created); $doc->upload($xml, $contentType);
Retries
SP-API applies rate limits. Clients from make() / makeGrantless() retry 429, selected 5xx responses, and connection errors by default, with exponential backoff and support for Retry-After.
AMAZON_SPAPI_RETRY_ENABLED=true AMAZON_SPAPI_RETRY_MAX_ATTEMPTS=3 AMAZON_SPAPI_RETRY_BASE_DELAY_MS=500
Disable per client:
AmazonSpApi::make(OrdersV0Api::class, options: ['retry' => false]);
Queued reports & feeds
Report and feed flows: create → poll → download → event.
use Tims\AmazonSpApi\Events\ReportDocumentReady; use Tims\AmazonSpApi\Jobs\RequestReportJob; RequestReportJob::dispatch( reportType: 'GET_MERCHANT_LISTINGS_DATA', marketplaceIds: ['ATVPDKIKX0DER'], // credentialsId: $credentials->id, // multi-seller ); Event::listen(ReportDocumentReady::class, function (ReportDocumentReady $event) { Storage::put("reports/{$event->reportId}.tsv", $event->contents); });
Feeds:
use Tims\AmazonSpApi\Jobs\SubmitFeedJob; SubmitFeedJob::dispatch( feedType: 'POST_PRODUCT_PRICING_DATA', marketplaceIds: ['ATVPDKIKX0DER'], contents: $xml, // or contentsPath: storage_path('feeds/price.xml') );
Events: ReportDocumentReady, ReportFailed, FeedResultReady, FeedFailed.
Poll settings: AMAZON_SPAPI_REPORT_POLL_SECONDS, AMAZON_SPAPI_FEED_POLL_SECONDS, AMAZON_SPAPI_MAX_POLL_ATTEMPTS.
Testing fakes
In tests, swap SpApiManager so make() returns Mockery doubles:
use SpApi\Api\reports\v2021_06_30\ReportsApi; use Tims\AmazonSpApi\Testing\SpApiFake; $fake = SpApiFake::start(); $fake->mock(ReportsApi::class, function ($api) { $api->shouldReceive('getReport')->andReturn($report); }); $api = AmazonSpApi::make(ReportsApi::class);
Access token caching
LWA access tokens and RDTs are stored in Laravel’s cache. Updating a Credentials row clears that seller’s cached tokens when the cache driver supports tags (for example Redis).
License
This package is open-sourced software licensed under the MIT license.
Copyright (c) 2026 TIMS.