kbart / amazon-ads-sdk
Amazon Ads API SDK for Laravel
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
- illuminate/cache: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/routing: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0|^13.0
- symplify/easy-coding-standard: ^13.0
This package is auto-updated.
Last update: 2026-04-02 22:53:55 UTC
README
A developer-friendly (DX) Laravel package for integrating the Amazon Ads API.
This package combines the type safety of a generated OpenAPI SDK with the convenience of dynamic Laravel proxy routes. It handles the complete authentication (LWA Token Refresh), rate limiting, and DTO hydration for you.
💎 Features
- Magic LWA Token Manager: Fully automatically fetches and refreshes Access Tokens via Amazon "Login with Amazon" and caches them securely in Redis/Cache.
- Smart Retries: Catches rate limits (
429) and server errors (5xx) and automatically retries requests using exponential backoff. - Multi-Seller Ready: Authentication is handled dynamically via the
{seller}route parameter. - Debugging & Agent: Easy control over Guzzle debug output and User-Agent headers.
- Dual Architecture:
- Direct SDK Usage (via Facade): Full autocompletion and type safety for backend jobs (cron jobs, queues).
- Dynamic SDK Proxy: A universal interface (
/api/amazon-ads/sdk/{seller}/...) for the frontend (Vue/React) with fully automatic DTO routing.
- Self-Documenting API: An info route (
/api/amazon-ads/sdk/{seller}/info/...) that resolves the entire Amazon Ads API, including Enums and Schemas, using PHP Reflection.
⚡ Installation
- Install the package via Composer
composer require kbart/amazon-ads-sdk
- Publish the configuration file
php artisan vendor:publish --tag="amazon-ads-config"
- Add your default credentials to your
.envfile
AMAZON_ADS_DEFAULT_CLIENT_ID="amzn1.application-oa2-client.xxxx" AMAZON_ADS_DEFAULT_CLIENT_SECRET="amzn1.oa2-client.v1.xxxx" AMAZON_ADS_DEFAULT_REFRESH_TOKEN="Atzr|xxxx" AMAZON_ADS_DEFAULT_REGION="EU" # EU, NA, or FE AMAZON_ADS_DEFAULT_PROFILE_ID="xxx"
- Routes & Middleware
By default, the package registers the SDK routes under the prefix api/amazon-ads/sdk/{seller}. To use the SDK routes, you must manually load them in your routes/api.php. You can also assign your own middleware:
use KBart\AmazonAds\AmazonAds; use App\Http\Middleware\Authenticate; // Registers the routes under api/amazon-ads/sdk/{seller}/... AmazonAds::routes([ 'middleware' => [Authenticate::class, 'your-custom-access-check'], ]);
(If you register the routes in your web.php, you must either include the 'api' middleware or exclude the routes from the CSRF token check in your bootstrap/app.php!)
Usage
1. The SdkProxy (Recommended)
The SdkProxyController receives HTTP requests, forwards them to the corresponding classes of the generated SDK, and returns the result as clean JSON.
Example Request (POST): Request to your local Laravel endpoint to fetch keywords.
POST /api/amazon-ads/sdk/{seller}/SponsoredProduct/Keywords/listSponsoredProductsKeywords Content-Type: application/json
{
"sponsoredProductsListSponsoredProductsKeywordsRequestContent": {
"maxResults": 100,
"stateFilter": {
"include": [
"ENABLED",
"PAUSED"
]
}
}
}
(The AmazonAdsExecutor recognizes that the sponsoredProductsListSponsoredProductsKeywordsRequestContent parameter requires a complex SDK object and safely casts your JSON array into this DTO before calling the SDK method.)
A. Docs / Info Route (Self-Documenting API)
Want to know which APIs are available and what the payload should look like?
Send a GET request to the info routes:
- All Verticals:
GET /api/amazon-ads/sdk/{seller}/info - All Resources (e.g., SponsoredProducts):
GET /api/amazon-ads/sdk/{seller}/info/sponsored-products - All Methods (e.g., Campaigns):
GET /api/amazon-ads/sdk/{seller}/info/sponsored-products/campaigns
The response will show you exactly which parameters are expected, whether a complex DTO needs to be sent, and which Enum values are allowed!
2. The Native Proxy
If you want to bypass the SDK completely and instead call the Amazon endpoints directly using Guzzle/Laravel HTTP, use the ProxyController.
Example Request (POST):
POST /api/amazon-ads/proxy/sp/keywords/list?seller={seller} Content-Type: application/json X-Amazon-Profile-Id: 1234567890
{
"maxResults": 100,
"stateFilter": {
"include":[
"ENABLED"
]
}
}
3. Direct Usage in PHP (Facades)
If you are building complex calculations or nightly synchronizations, you should use the generated SDK classes directly.
You can comfortably use the AmazonAdsFactory facade for this. The package handles token injection and rate-limit retries in the background!
namespace App\Http\Controllers; use Illuminate\Http\Request; use KBart\AmazonAds\Facades\AmazonAdsSdk; use KBart\AmazonAds\Auth\AmazonAdsCredentials; use KBart\AmazonAds\Generated\SponsoredProducts\Api\CampaignsApi; // The facade takes care of EVERYTHING: // Token Refresh, Retries, Rate Limits AND the automatic // hydration of the array into complex Amazon DTOs! class CustomAmazonController extends Controller { // Via Request: Pass the Request, as well as the Namespace, desired Api Class and Method. public function createCampaignWithRequest(Request $request) { return $this->handleApiCall(function () use ($request) { return AmazonAdsSdk::execute( request: $request, namespace: 'SponsoredProduct', apiClass: CampaignsApi::class, method: 'listSponsoredProductsCampaigns' ); }); } // Via Payload: Pass the Payload, as well as the Namespace, desired Api Class and Method. public function createCampaignWithArray(array $payload) { return $this->handleApiCall(function () use ($payload) { $credentials = AmazonAdsCredentials::fromArray($payload); return AmazonAdsSdk::executeWithCredentials( credentials: $credentials, namespace: 'SponsoredProduct', apiClass: CampaignsApi::class, method: 'createCampaigns', payload: $payload ); }); } }
👨💼 Advanced
1. Custom Credentials Resolver
By default, the package uses the .env variables. If you want to store your seller data in a database, for example, you can define your own credentials resolver in the AppServiceProvider of your Laravel application.
From now on, the package will always call this logic when executing API calls!
// in app/Providers/AppServiceProvider.php use Illuminate\Support\Facades\Log; use Illuminate\Support\Carbon; use Illuminate\Validation\Rule; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; use KBart\AmazonAds\AmazonAds; use KBart\AmazonAds\Auth\AmazonAdsCredentials; use App\Models\Seller; public function boot(): void { AmazonAds::resolveCredentialsUsing(function (Request $request) { $request->mergeIfMissing([ 'seller' => $request->route('seller', $request->query('seller', $request->input('seller', null))), 'endpoint' => 'EU', 'countryCode' => 'DE', ]); $validated = $request->validate([ 'seller' => [ 'required', 'string', Rule::exists(Seller::class, 'seller'), ], 'endpoint' => ['required', 'string'], 'countryCode' => ['required', 'string'], ]); $seller = Seller::where('seller', $validated['seller']) ->whereHas('logins', function ($query) use ($validated) { $query->where('endpoint', $validated['endpoint']) ->whereHas('profiles', function ($q) use ($validated) { $q->where('countryCode', $validated['countryCode']); }); }) ->with([ 'logins' => function ($query) use ($validated) { $query->where('endpoint', $validated['endpoint']) ->limit(1) ->with([ 'profiles' => function ($q) use ($validated) { $q->where('countryCode', $validated['countryCode']) ->limit(1); }, ]); }, ]) ->first(); if (empty($seller)) { Log::error("Doesn't find Seller {$validated['seller']} with Login for endpoint {$validated['endpoint']} or profile for {$validated['countryCode']}, in Database."); throw new \InvalidArgumentException("Can't find Sellerprofile {$validated['seller']}"); } $login = $seller->logins->first(); $profile = $login->profiles->first(); if (! empty($login->clientSecretExpiry)) { if (Carbon::parse($login->clientSecretExpiry)->subDays(3)->lessThanOrEqualTo(now())) { Log::error("clientSecret of {$seller->seller} is very close to expire!", [ 'expiresAt' => $login->clientSecretExpiry, 'hoursLeft' => Carbon::parse($login->clientSecretExpiry)->diffInHours(now()), ]); } elseif (Carbon::parse($login->clientSecretExpiry)->subDays(14)->lessThanOrEqualTo(now())) { Log::info("clientSecret of {$seller->seller} is about to expire!", [ 'expiresAt' => $login->clientSecretExpiry, 'daysLeft' => Carbon::parse($login->clientSecretExpiry)->diffInDays(now()), ]); } } return new AmazonAdsCredentials( clientId: $login->clientId, clientSecret: $login->clientSecret, refreshToken: $login->refreshToken, region: $login->endpoint, profileId: $profile->profileId, ); }); }
2. Set a custom user-agent
Amazon does not strictly require a specific User-Agent; it can be determined by you and adjusted in the config if necessary.
'settings' => [ 'user-agent' => 'Your-User-Agent/1.0.0/PHP', ]
3. Caching & TTL Configuration
In config/amazon-ads.php, specific TTL values for caching endpoints can be defined.
'cache-ttl' => [ 'catalogItemsV20220401' => [ 'getCatalogItem' => 3600, // cache for 1 hour 'searchCatalogItems' => 600, // cache for 10 minutes ], 'productPricingV0' => [ 'getPricing' => 300, // cache for 5 minutes ], ]
Enable Debugging
If you want to enable debugging for the SDK, adjust the corresponding parameters in the config.
'settings' => [ 'debug' => true, 'debugFile' => base_path('storage/logs/amazon-ads-sdk.log'), ]
🛠️ SDK Generation (Build Process)
This SDK uses the official OpenAPI Specifications from Amazon to always remain 100% type-safe and up to date. The required .json and .yaml specification files are now located directly within your package in the ./resources/openapi/ directory. An automated pipeline based on Node.js and PHP has been built, generating ready-to-use classes for you at the push of a button. This utilizes the official openapi-generator-cli tool. The build script also automatically fixes known Amazon OpenAPI bugs and cleans up the endpoints.
Prerequisites
Ensure that you have Node.js and npm installed on your system. Before running for the first time, install the necessary dependencies in the root directory of the package:
npm install
1. Regenerate all endpoints
If you want to bring the entire SDK up to date, run this command:
npm run build:all
(This is equivalent to npm run clean && npm run generate)
What happens in the background?
- The
src/Generatedfolder is completely emptied. - The script loads the OpenAPI files directly from the local
./resources/openapi/directory. - A pre-processor script cleans the OpenAPI spec files in
.jsonformat and removes problematic Content-Types likeapplication/jsonifapplication/vnd...is present. - The
openapi-generator-cligenerates all Models (DTOs) and APIs. - All redundant boilerplate files are cleaned up (
cleanup.js). (Uses rimraf under the hood). - A PHP script (
generate_api_map.php) scans the new classes and fully automatically creates thesrc/Laravel/amazon_api_map.phpfile, which acts as a lightning-fast registry. - A post-processor script fixes duck-typing and import issues in the generated classes
ObjectSerializer.phpandFormDataProcessor.php.
2. Update a single endpoint
If Amazon has only updated one area (e.g., SponsoredProduct), you can specifically target individual endpoints without rebuilding everything:
npm run generate -- SponsoredProduct
(This only updates the SponsoredProduct classes and automatically regenerates the API map at the end, without deleting other endpoints).
3. Add a new endpoint
Has Amazon released a new API? No problem:
- Download the specification file from Amazon and save it in your
./resources/openapi/folder (e.g., asNewApi.json). - Open the
generate-apis.jsfile in the root directory. - Add a new entry to the
apisarray, referencing the local file path:{ name: 'GreatNewApi', input: './resources/openapi/NewApi.json' } - Run
npm run generate -- GreatNewApi. - Done! The API is now immediately available as a PHP class and is automatically registered in the Proxy Controller.
📄 License
This project is licensed under the Apache License 2.0.