TL;DR
Amazon Selling Partner API (SP-API) is a REST-based API that enables programmatic access to seller data for orders, inventory, listings, and fulfillment. It uses OAuth 2.0 authentication with IAM roles, requires AWS SigV4 signing, and enforces rate limits that vary by endpoint (0.1 to 100 requests per second). This guide covers account setup, authentication, core endpoints, webhook subscriptions, and production deployment strategies.
Introduction
Amazon processes over 350 million products across 200+ marketplaces worldwide. For developers building e-commerce tools, inventory management systems, or analytics platforms, Amazon SP-API integration isn’t optional. It’s essential.
Here’s the reality: sellers managing Amazon operations lose 20-30 hours weekly on manual data entry across orders, inventory, and listings. A solid SP-API integration automates order synchronization, inventory updates, and listing management across multiple marketplaces.
This guide walks through the complete Amazon SP-API integration process. You’ll learn IAM role setup, OAuth 2.0 authorization, AWS SigV4 request signing, order and inventory management, notification subscriptions, and error troubleshooting. By the end, you’ll have a production-ready Amazon integration.
What Is Amazon SP-API?
Amazon Selling Partner API (SP-API) is a REST-based API that provides programmatic access to seller central data. It replaced the older Marketplace Web Service (MWS) with improved security, performance, and functionality.
Key Capabilities
SP-API handles:
- Order retrieval and status updates
- Inventory management across marketplaces
- Listing creation, updates, and deletion
- FBA (Fulfillment by Amazon) shipment management
- Product pricing and competitive analysis
- Reports and analytics generation
- A+ Content management
- Brand analytics and advertising data
SP-API vs MWS Comparison
| Feature | SP-API | MWS (Legacy) |
|---|---|---|
| Architecture | RESTful JSON | XML-based |
| Authentication | OAuth 2.0 + IAM | MWS Auth Token |
| Security | AWS SigV4 signing | Simple tokens |
| Rate Limits | Dynamic per endpoint | Fixed quotas |
| Marketplaces | Unified endpoints | Region-specific |
| Status | Current | Deprecated (Dec 2025) |
Migrate any MWS integrations to SP-API immediately. Amazon announced full MWS retirement for December 2025.
API Architecture Overview
Amazon uses a regional API structure with centralized authorization:
https://sellingpartnerapi-na.amazon.com (North America)
https://sellingpartnerapi-eu.amazon.com (Europe)
https://sellingpartnerapi-fe.amazon.com (Far East)
All requests require:
- AWS SigV4 signature
- Access token from OAuth flow
- Proper IAM role permissions
- Request ID for tracing
Supported Marketplaces
| Region | Marketplaces | API Endpoint |
|---|---|---|
| North America | US, CA, MX | sellingpartnerapi-na.amazon.com |
| Europe | UK, DE, FR, IT, ES, NL, SE, PL, TR, EG, IN, AE, SA | sellingpartnerapi-eu.amazon.com |
| Far East | JP, AU, SG, BR | sellingpartnerapi-fe.amazon.com |
Getting Started: Account and IAM Setup
Step 1: Create Your Amazon Developer Account
Before accessing SP-API, you need proper account access:
- Visit the Amazon Developer Central
- Sign in with your Amazon account (must have Seller Central access)
- Navigate to Selling Partner API in the dashboard
- Accept the Developer Agreement
Step 2: Register Your Application
Create an application profile in Seller Central:
- Log into Seller Central
- Navigate to Apps and Services > Develop Apps
- Click Add New App
- Fill in application details:
- Application Name: Clear, descriptive name
- Application Type: Select “Self-developed” or “Third-party”
- Use Case: Describe your integration purpose
- Redirect URI: HTTPS URL for OAuth callback
After submission, you’ll receive:
- Application ID: Your public application identifier
- Client ID: Used in OAuth authorization URL
- Client Secret: Your private API secret (never expose this)
Security note: Store credentials in environment variables, never in code:
# .env file
AMAZON_APPLICATION_ID="amzn1.application.xxxxx"
AMAZON_CLIENT_ID="amzn1.account.xxxxx"
AMAZON_CLIENT_SECRET="your_client_secret_here"
AMAZON_SELLER_ID="your_seller_id_here"
AWS_ACCESS_KEY_ID="your_aws_access_key"
AWS_SECRET_ACCESS_KEY="your_aws_secret_key"
AWS_REGION="us-east-1"
Step 3: Create IAM Role for SP-API
SP-API requires an IAM role with specific permissions:
- Log into AWS IAM Console
- Navigate to Roles > Create Role
- Select Another AWS account as trusted entity
- Enter Amazon’s account ID for your region:
- North America:
906394416454 - Europe:
336853085554 - Far East:
774466381866
Step 4: Configure IAM Policy
Attach this policy to your IAM role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"execute-api:Invoke"
],
"Resource": [
"arn:aws:execute-api:*:*:*/prod/*/sellingpartnerapi/*"
]
}
]
}
Name your role something descriptive like SellingPartnerApiRole and note the ARN.
Step 5: Link IAM Role to Application
Connect your IAM role to the SP-API application:
- Return to Seller Central > Develop Apps
- Select your application
- Click Edit > IAM Role ARN
- Enter your IAM role ARN
- Save changes
Amazon validates the IAM role within minutes. You’ll see a “Linked” status when ready.
OAuth 2.0 Authentication Flow
Understanding SP-API OAuth
Amazon uses OAuth 2.0 for authorization. Here’s the complete flow:
1. Seller clicks "Authorize" in your application
2. Your app redirects to Amazon authorization URL
3. Seller logs in and grants permissions
4. Amazon redirects back with authorization code
5. Your app exchanges code for LWA (Login with Amazon) token
6. Your app exchanges LWA token for SP-API access token
7. Your app uses access token for API calls (SigV4 signed)
8. Refresh token when access token expires (1 hour)
Step 6: Generate Authorization URL
Create the OAuth authorization URL:
const generateAuthUrl = (clientId, redirectUri, state) => {
const baseUrl = 'https://www.amazon.com/sp/apps/oauth/authorize';
const params = new URLSearchParams({
application_id: process.env.AMAZON_APPLICATION_ID,
client_id: clientId,
redirect_uri: redirectUri,
state: state, // Random string for CSRF protection
scope: 'sellingpartnerapi::notifications'
});
return `${baseUrl}?${params.toString()}`;
};
// Usage
const authUrl = generateAuthUrl(
process.env.AMAZON_CLIENT_ID,
'https://your-app.com/callback',
crypto.randomBytes(16).toString('hex')
);
console.log(`Redirect user to: ${authUrl}`);
Required OAuth Scopes
Request only the permissions your application needs:
| Scope | Description | Use Case |
|---|---|---|
sellingpartnerapi::notifications |
Receive notifications | Webhook subscriptions |
sellingpartnerapi::migration |
Migrate from MWS | Legacy integrations |
Most API access is controlled by IAM policies, not OAuth scopes.
Step 7: Exchange Code for LWA Token
Handle the OAuth callback and exchange the authorization code:
const exchangeCodeForLwaToken = async (code, redirectUri) => {
const response = await fetch('https://api.amazon.com/auth/o2/token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: process.env.AMAZON_CLIENT_ID,
client_secret: process.env.AMAZON_CLIENT_SECRET,
redirect_uri: redirectUri,
code: code
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`LWA Token Error: ${error.error_description}`);
}
const data = await response.json();
return {
access_token: data.access_token,
refresh_token: data.refresh_token,
expires_in: data.expires_in, // Typically 3600 seconds (1 hour)
token_type: data.token_type
};
};
// Handle callback route
app.get('/callback', async (req, res) => {
const { spapi_oauth_code, state } = req.query;
// Verify state matches what you sent (CSRF protection)
if (state !== req.session.oauthState) {
return res.status(400).send('Invalid state parameter');
}
try {
const tokens = await exchangeCodeForLwaToken(spapi_oauth_code, 'https://your-app.com/callback');
// Store tokens in database associated with seller
await db.sellers.update(req.session.sellerId, {
amazon_lwa_access_token: tokens.access_token,
amazon_lwa_refresh_token: tokens.refresh_token,
amazon_token_expires: Date.now() + (tokens.expires_in * 1000)
});
res.redirect('/dashboard');
} catch (error) {
console.error('Token exchange failed:', error);
res.status(500).send('Authentication failed');
}
});
Step 8: Exchange LWA Token for SP-API Credentials
Use the LWA access token to get temporary AWS credentials:
const assumeRole = async (lwaAccessToken) => {
const response = await fetch('https://api.amazon.com/auth/o2/token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.AMAZON_CLIENT_ID,
client_secret: process.env.AMAZON_CLIENT_SECRET,
scope: 'sellingpartnerapi::notifications'
})
});
const data = await response.json();
// Exchange for AWS credentials via STS
const stsResponse = await fetch('https://sts.amazonaws.com/', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Bearer ${data.access_token}`
},
body: new URLSearchParams({
Action: 'AssumeRole',
RoleArn: 'arn:aws:iam::YOUR_ACCOUNT:role/SellingPartnerApiRole',
RoleSessionName: 'sp-api-session',
Version: '2011-06-15'
})
});
return stsResponse;
};
Step 9: Implement Token Refresh
Access tokens expire after 1 hour. Implement automatic refresh:
const refreshLwaToken = async (refreshToken) => {
const response = await fetch('https://api.amazon.com/auth/o2/token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'refresh_token',
client_id: process.env.AMAZON_CLIENT_ID,
client_secret: process.env.AMAZON_CLIENT_SECRET,
refresh_token: refreshToken
})
});
const data = await response.json();
return {
access_token: data.access_token,
refresh_token: data.refresh_token, // Always save the new refresh token
expires_in: data.expires_in
};
};
// Middleware to ensure valid token
const ensureValidToken = async (sellerId) => {
const seller = await db.sellers.findById(sellerId);
// Check if token expires within 5 minutes
if (seller.amazon_token_expires < Date.now() + 300000) {
const newTokens = await refreshLwaToken(seller.amazon_lwa_refresh_token);
await db.sellers.update(sellerId, {
amazon_lwa_access_token: newTokens.access_token,
amazon_lwa_refresh_token: newTokens.refresh_token,
amazon_token_expires: Date.now() + (newTokens.expires_in * 1000)
});
return newTokens.access_token;
}
return seller.amazon_lwa_access_token;
};
AWS SigV4 Request Signing
Understanding SigV4
All SP-API requests require AWS Signature Version 4 (SigV4) signing. This ensures request authenticity and integrity.
SigV4 Signing Process
const crypto = require('crypto');
class SigV4Signer {
constructor(accessKey, secretKey, region, service = 'execute-api') {
this.accessKey = accessKey;
this.secretKey = secretKey;
this.region = region;
this.service = service;
}
sign(method, url, body = '', headers = {}) {
const parsedUrl = new URL(url);
const now = new Date();
// Step 1: Create canonical request
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, '');
const dateStamp = amzDate.slice(0, 8);
headers['host'] = parsedUrl.host;
headers['x-amz-date'] = amzDate;
headers['x-amz-access-token'] = this.accessToken;
headers['content-type'] = 'application/json';
const canonicalHeaders = Object.entries(headers)
.sort(([a], [b]) => a.localeCompare(b))
.map(([k, v]) => `${k.toLowerCase()}:${v.trim()}`)
.join('\n');
const signedHeaders = Object.keys(headers)
.sort()
.map(k => k.toLowerCase())
.join(';');
const payloadHash = crypto.createHash('sha256').update(body).digest('hex');
const canonicalRequest = [
method.toUpperCase(),
parsedUrl.pathname,
parsedUrl.search.slice(1),
canonicalHeaders,
'',
signedHeaders,
payloadHash
].join('\n');
// Step 2: Create string to sign
const algorithm = 'AWS4-HMAC-SHA256';
const credentialScope = `${dateStamp}/${this.region}/${this.service}/aws4_request`;
const stringToSign = [
algorithm,
amzDate,
credentialScope,
crypto.createHash('sha256').update(canonicalRequest).digest('hex')
].join('\n');
// Step 3: Calculate signature
const kDate = this.hmac(`AWS4${this.secretKey}`, dateStamp);
const kRegion = this.hmac(kDate, this.region);
const kService = this.hmac(kRegion, this.service);
const kSigning = this.hmac(kService, 'aws4_request');
const signature = this.hmac(kSigning, stringToSign, 'hex');
// Step 4: Add authorization header
const authorization = `${algorithm} Credential=${this.accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
return {
headers: {
...headers,
'Authorization': authorization
},
canonicalRequest,
stringToSign,
signature
};
}
hmac(key, data, encoding = 'buffer') {
return crypto.createHmac('sha256', key).update(data).digest(encoding);
}
}
// Usage
const signer = new SigV4Signer(
process.env.AWS_ACCESS_KEY_ID,
process.env.AWS_SECRET_ACCESS_KEY,
'us-east-1'
);
const signedRequest = signer.sign('GET', 'https://sellingpartnerapi-na.amazon.com/orders/v0/orders', '', {
'x-amz-access-token': accessToken
});
Using AWS SDK for SigV4
Simplify signing with the AWS SDK:
const { SignatureV4 } = require('@aws-sdk/signature-v4');
const { Sha256 } = require('@aws-crypto/sha256-js');
const signer = new SignatureV4({
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
},
region: 'us-east-1',
service: 'execute-api',
sha256: Sha256
});
const makeSpApiRequest = async (method, endpoint, accessToken, body = null) => {
const url = new URL(endpoint);
const headers = {
'host': url.host,
'content-type': 'application/json',
'x-amz-access-token': accessToken,
'x-amz-date': new Date().toISOString().replace(/[:-]|\.\d{3}/g, '')
};
const signedRequest = await signer.sign({
method,
hostname: url.hostname,
path: url.pathname,
query: Object.fromEntries(url.searchParams),
headers,
body: body ? JSON.stringify(body) : undefined
});
const response = await fetch(endpoint, {
method,
headers: signedRequest.headers,
body: signedRequest.body
});
if (!response.ok) {
const error = await response.json();
throw new Error(`SP-API Error: ${error.errors?.[0]?.message || response.statusText}`);
}
return response.json();
};
Orders API
Retrieving Orders
Fetch orders with filtering options:
const getOrders = async (accessToken, options = {}) => {
const params = new URLSearchParams({
createdAfter: options.createdAfter, // ISO 8601 format
createdBefore: options.createdBefore,
orderStatuses: options.orderStatuses?.join(',') || '',
marketplaceIds: options.marketplaceIds?.join(',') || ['ATVPDKIKX0DER'], // US
maxResultsPerPage: options.maxResultsPerPage || 100
});
// Remove empty params
for (const [key, value] of params.entries()) {
if (!value) params.delete(key);
}
const endpoint = `https://sellingpartnerapi-na.amazon.com/orders/v0/orders?${params.toString()}`;
return makeSpApiRequest('GET', endpoint, accessToken);
};
// Usage example
const orders = await getOrders(accessToken, {
createdAfter: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), // Last 24 hours
orderStatuses: ['Unshipped', 'PartiallyShipped'],
marketplaceIds: ['ATVPDKIKX0DER'] // US marketplace
});
Order Response Structure
{
"payload": {
"orders": [
{
"amazon_order_id": "112-1234567-1234567",
"seller_order_id": "ORDER-001",
"purchase_date": "2026-03-19T10:30:00Z",
"last_update_date": "2026-03-19T14:45:00Z",
"order_status": "Unshipped",
"fulfillment_channel": "AFN", // AFN = FBA, MFN = Merchant
"sales_channel": "Amazon.com",
"order_channel": "Amazon.com",
"ship_service_level": "Std US D2D Dom",
"order_total": {
"currency_code": "USD",
"amount": "89.99"
},
"number_of_items_shipped": 0,
"number_of_items_unshipped": 2,
"payment_execution_detail": [],
"payment_method": "CreditCard",
"payment_method_details": ["CreditCard"],
"marketplace_id": "ATVPDKIKX0DER",
"shipment_service_level_category": "Standard",
"easy_ship_shipment_status": null,
"is_business_order": false,
"is_prime": true,
"is_premium_order": false,
"is_global_express_enabled": false
}
],
"next_token": "eyJleHBpcmF0aW9uVGltZU9mTmV4dFRva2VuIjoxNzEwOTUwNDAwfQ=="
}
}
Getting Order Items
Retrieve detailed line items for an order:
const getOrderItems = async (accessToken, orderId) => {
const endpoint = `https://sellingpartnerapi-na.amazon.com/orders/v0/orders/${orderId}/orderItems`;
return makeSpApiRequest('GET', endpoint, accessToken);
};
// Usage
const orderItems = await getOrderItems(accessToken, '112-1234567-1234567');
// Expected response
{
"payload": {
"order_items": [
{
"asin": "B08N5WRWNW",
"seller_sku": "MYSKU-001",
"title": "Wireless Bluetooth Headphones",
"quantity_ordered": 2,
"quantity_shipped": 0,
"product_info": {
"number_of_items": 2
},
"item_price": {
"currency_code": "USD",
"amount": "44.99"
},
"item_total": {
"currency_code": "USD",
"amount": "89.98"
},
"tax_collection": {
"tax_collection_model": "MarketplaceFacilitator",
"responsible_party": "Amazon Services, Inc."
}
}
]
}
}
Updating Shipment Status
Mark orders as shipped with tracking information:
const confirmShipment = async (accessToken, orderId, shipmentData) => {
const endpoint = `https://sellingpartnerapi-na.amazon.com/orders/v0/orders/${orderId}/shipmentConfirmation`;
const payload = {
packageDetails: {
packageReferenceId: shipmentData.packageReferenceId || '1',
carrier_code: shipmentData.carrierCode, // e.g., 'USPS', 'FEDEX', 'UPS'
tracking_number: shipmentData.trackingNumber,
ship_date: shipmentData.shipDate || new Date().toISOString(),
items: shipmentData.items.map(item => ({
order_item_id: item.orderItemId,
quantity: item.quantity
}))
}
};
return makeSpApiRequest('POST', endpoint, accessToken, payload);
};
// Usage
await confirmShipment(accessToken, '112-1234567-1234567', {
carrierCode: 'USPS',
trackingNumber: '9400111899223456789012',
items: [
{ orderItemId: '12345678901234', quantity: 2 }
]
});
Common Carrier Codes
| Carrier | Carrier Code |
|---|---|
| USPS | USPS |
| FedEx | FEDEX |
| UPS | UPS |
| DHL | DHL |
| Canada Post | CANADA_POST |
| Royal Mail | ROYAL_MAIL |
| Australia Post | AUSTRALIA_POST |
| Amazon Logistics | AMZN_UK |
Inventory API
Getting Inventory Summaries
Fetch inventory levels across marketplaces:
const getInventorySummaries = async (accessToken, options = {}) => {
const params = new URLSearchParams({
granularityType: options.granularityType || 'Marketplace',
granularityId: options.granularityId || 'ATVPDKIKX0DER', // US
startDateTime: options.startDateTime || '',
sellerSkus: options.sellerSkus?.join(',') || ''
});
const endpoint = `https://sellingpartnerapi-na.amazon.com/fba/inventory/v1/summaries?${params.toString()}`;
return makeSpApiRequest('GET', endpoint, accessToken);
};
// Usage
const inventory = await getInventorySummaries(accessToken, {
granularityId: 'ATVPDKIKX0DER',
sellerSkus: ['MYSKU-001', 'MYSKU-002']
});
Inventory Response Structure
{
"payload": {
"inventorySummaries": [
{
"asin": "B08N5WRWNW",
"seller_sku": "MYSKU-001",
"condition": "NewItem",
"details": {
"quantity": 150,
"fulfillable_quantity": 145,
"inbound_working_quantity": 0,
"inbound_shipped_quantity": 5,
"inbound_receiving_quantity": 0,
"reserved_quantity": 5,
"unfulfillable_quantity": 0,
"warehouse_damage_quantity": 0,
"distributor_damaged_quantity": 0,
"carrier_damaged_quantity": 0,
"defective_quantity": 0,
"customer_damaged_quantity": 0
},
"marketplace_id": "ATVPDKIKX0DER"
}
]
}
}
Updating Inventory
Note: SP-API doesn’t provide direct inventory update endpoints. Inventory is managed through:
- FBA shipments - Send inventory to Amazon warehouses
- MFN orders - Inventory decreases automatically when orders ship
- Listing updates - Adjust quantity through Listings API
For FBA, create shipment plans:
const createInboundShipmentPlan = async (accessToken, shipmentData) => {
const endpoint = 'https://sellingpartnerapi-na.amazon.com/fba/inbound/v0/plans';
const payload = {
ShipFromAddress: {
Name: shipmentData.shipFromName,
AddressLine1: shipmentData.shipFromAddress,
City: shipmentData.shipFromCity,
StateOrProvinceCode: shipmentData.shipFromState,
CountryCode: shipmentData.shipFromCountry,
PostalCode: shipmentData.shipFromPostalCode
},
LabelPrepPreference: 'SELLER_LABEL',
InboundPlanItems: shipmentData.items.map(item => ({
SellerSKU: item.sku,
ASIN: item.asin,
Quantity: item.quantity,
Condition: 'NewItem'
}))
};
return makeSpApiRequest('POST', endpoint, accessToken, payload);
};
Listings API
Getting Listings
Fetch product listings with filtering:
const getListings = async (accessToken, options = {}) => {
const params = new URLSearchParams({
marketplaceIds: options.marketplaceIds?.join(',') || ['ATVPDKIKX0DER'],
itemTypes: options.itemTypes?.join(',') || ['ASIN', 'SKU'],
identifiers: options.identifiers?.join(',') || '',
issuesLocale: options.locale || 'en_US'
});
const endpoint = `https://sellingpartnerapi-na.amazon.com/listings/2021-08-01/items?${params.toString()}`;
return makeSpApiRequest('GET', endpoint, accessToken);
};
// Usage
const listings = await getListings(accessToken, {
identifiers: ['B08N5WRWNW', 'B09JQKJXYZ'],
itemTypes: ['ASIN']
});
Listing Response Structure
{
"identifiers": {
"marketplaceId": "ATVPDKIKX0DER",
"sku": "MYSKU-001",
"asin": "B08N5WRWNW"
},
"attributes": {
"title": "Wireless Bluetooth Headphones",
"description": "Premium wireless headphones with noise cancellation",
"brand": "MyBrand",
"color": "Black",
"size": "One Size",
"item_weight": "0.5 pounds",
"product_dimensions": "7 x 6 x 3 inches"
},
"product_type": "LUGGAGE",
"sales_price": {
"currency_code": "USD",
"amount": "89.99"
},
"list_price": {
"currency_code": "USD",
"amount": "129.99"
},
"fulfillment_availability": [
{
"fulfillment_channel_code": "AFN",
"quantity": 150
}
],
"condition_type": "New",
"status": "ACTIVE",
"procurement": null
}
Creating or Updating Listings
Use submitListingsSubmission for batch operations:
const submitListingUpdate = async (accessToken, listingData) => {
const endpoint = 'https://sellingpartnerapi-na.amazon.com/listings/2021-08-01/items/MYSKU-001';
const payload = {
productType: 'LUGGAGE',
patches: [
{
op: 'replace',
path: '/attributes/title',
value: 'Updated Wireless Bluetooth Headphones - Premium Sound'
},
{
op: 'replace',
path: '/salesPrice',
value: {
currencyCode: 'USD',
amount: '79.99'
}
}
]
};
return makeSpApiRequest('PATCH', endpoint, accessToken, payload);
};
Deleting a Listing
Remove or deactivate a listing:
const deleteListing = async (accessToken, sku, marketplaceIds) => {
const params = new URLSearchParams({
marketplaceIds: marketplaceIds.join(',')
});
const endpoint = `https://sellingpartnerapi-na.amazon.com/listings/2021-08-01/items/${sku}?${params.toString()}`;
return makeSpApiRequest('DELETE', endpoint, accessToken);
};
Reports API
Creating Report Schedules
Automate report generation:
const createReport = async (accessToken, reportType, dateRange) => {
const endpoint = 'https://sellingpartnerapi-na.amazon.com/reports/2021-06-30/reports';
const payload = {
reportType: reportType,
marketplaceIds: dateRange.marketplaceIds || ['ATVPDKIKX0DER'],
dataStartTime: dateRange.startTime?.toISOString(),
dataEndTime: dateRange.endTime?.toISOString()
};
return makeSpApiRequest('POST', endpoint, accessToken, payload);
};
// Common report types
const REPORT_TYPES = {
ORDERS: 'GET_FLAT_FILE_ALL_ORDERS_DATA_BY_LAST_UPDATE_GENERAL',
ORDER_ITEMS: 'GET_FLAT_FILE_ORDER_ITEMS_DATA_BY_LAST_UPDATE_GENERAL',
INVENTORY: 'GET_MERCHANT_LISTINGS_ALL_DATA',
FBA_INVENTORY: 'GET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA',
SETTLEMENT: 'GET_V2_SETTLEMENT_REPORT_DATA_FLAT_FILE',
SALES_AND_TRAFFIC: 'GET_SALES_AND_TRAFFIC_REPORT',
ADVERTISING: 'GET_BRAND_ANALYTICS_SEARCH_TERMS_REPORT'
};
// Usage
const report = await createReport(accessToken, REPORT_TYPES.ORDERS, {
marketplaceIds: ['ATVPDKIKX0DER'],
startTime: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // Last 7 days
endTime: new Date()
});
Getting Report Document
Download the generated report:
const getReportDocument = async (accessToken, reportId) => {
const endpoint = `https://sellingpartnerapi-na.amazon.com/reports/2021-06-30/reports/${reportId}/document`;
return makeSpApiRequest('GET', endpoint, accessToken);
};
// Download and parse report
const downloadReport = async (accessToken, reportId) => {
const documentInfo = await getReportDocument(accessToken, reportId);
const response = await fetch(documentInfo.payload.url);
const content = await response.text();
// Reports are typically tab-delimited or JSON
if (documentInfo.payload.compressionAlgorithm === 'GZIP') {
const decompressed = await decompressGzip(content);
return decompressed;
}
return content;
};
Notifications API
Creating Subscriptions
Set up webhooks for real-time events:
const createSubscription = async (accessToken, subscriptionData) => {
const endpoint = 'https://sellingpartnerapi-na.amazon.com/notifications/v1/subscriptions';
const payload = {
payload: {
destination: {
resource: subscriptionData.destinationArn, // SNS topic ARN
name: subscriptionData.name
},
modelVersion: '1.0',
eventFilter: {
eventCode: subscriptionData.eventCode,
marketplaceIds: subscriptionData.marketplaceIds
}
}
};
return makeSpApiRequest('POST', endpoint, accessToken, payload);
};
// Available event types
const EVENT_CODES = {
ORDER_STATUS_CHANGE: 'OrderStatusChange',
ORDER_ITEM_CHANGE: 'OrderItemChange',
ORDER_CHANGE: 'OrderChange',
FBA_ORDER_STATUS_CHANGE: 'FBAOrderStatusChange',
FBA_OUTBOUND_SHIPMENT_STATUS: 'FBAOutboundShipmentStatus',
INVENTORY_LEVELS: 'InventoryLevels',
PRICING_HEALTH: 'PricingHealth'
};
// Usage
await createSubscription(accessToken, {
destinationArn: 'arn:aws:sns:us-east-1:123456789012:sp-api-notifications',
name: 'OrderStatusNotifications',
eventCode: EVENT_CODES.ORDER_STATUS_CHANGE,
marketplaceIds: ['ATVPDKIKX0DER']
});
Setting Up SNS Destination
Amazon sends notifications to SNS topics:
const createSnsDestination = async (accessToken, destinationData) => {
const endpoint = 'https://sellingpartnerapi-na.amazon.com/notifications/v1/destinations';
const payload = {
resource: destinationData.snsTopicArn,
name: destinationData.name
};
return makeSpApiRequest('POST', endpoint, accessToken, { payload });
};
// SNS topic policy must allow Amazon SES to publish
const snsTopicPolicy = {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: {
Service: 'notifications.amazon.com'
},
Action: 'SNS:Publish',
Resource: 'arn:aws:sns:us-east-1:123456789012:sp-api-notifications'
}
]
};
Processing Notifications
Set up an SNS endpoint to receive notifications:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhooks/amazon', express.raw({ type: 'application/json' }), async (req, res) => {
const signature = req.headers['x-amz-sns-message-signature'];
const payload = req.body;
// Verify SNS message signature
const isValid = await verifySnsSignature(payload, signature);
if (!isValid) {
console.error('Invalid SNS signature');
return res.status(401).send('Unauthorized');
}
const message = JSON.parse(payload.toString());
// Handle different message types
switch (message.Type) {
case 'SubscriptionConfirmation':
// Auto-confirm subscription
await fetch(message.SubscribeURL);
break;
case 'Notification':
const notification = JSON.parse(message.Message);
await handleSpApiNotification(notification);
break;
}
res.status(200).send('OK');
});
async function handleSpApiNotification(notification) {
const { notificationType, payload } = notification;
switch (notificationType) {
case 'OrderStatusChange':
await syncOrderStatus(payload.amazonOrderId);
break;
case 'OrderChange':
await syncOrderDetails(payload.amazonOrderId);
break;
case 'InventoryLevels':
await updateInventoryCache(payload);
break;
}
}
Rate Limiting and Quotas
Understanding Rate Limits
SP-API enforces dynamic rate limits per endpoint:
| Endpoint Category | Rate Limit | Burst Limit |
|---|---|---|
| Orders | 10 requests/second | 20 |
| Order Items | 5 requests/second | 10 |
| Inventory | 2 requests/second | 5 |
| Listings | 10 requests/second | 20 |
| Reports | 0.5 requests/second | 1 |
| Notifications | 1 request/second | 2 |
| FBA Inbound | 2 requests/second | 5 |
Check the x-amzn-RateLimit-Limit header in responses for current limits.
Implementing Rate Limit Handling
Use exponential backoff for retries:
const makeRateLimitedRequest = async (method, endpoint, accessToken, body = null, maxRetries = 5) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await makeSpApiRequest(method, endpoint, accessToken, body);
// Check rate limit headers
const rateLimit = response.headers.get('x-amzn-RateLimit-Limit');
const retryAfter = response.headers.get('Retry-After');
if (retryAfter) {
console.warn(`Rate limited. Retry after: ${retryAfter} seconds`);
}
return response;
} catch (error) {
if (error.message.includes('429') && attempt < maxRetries) {
// Extract retry-after header or use exponential backoff
const retryAfter = error.headers?.get('Retry-After') || Math.pow(2, attempt);
console.log(`Rate limited. Retrying in ${retryAfter}s...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
} else if (error.message.includes('503') && attempt < maxRetries) {
// Service unavailable - exponential backoff
const delay = Math.pow(2, attempt) * 1000;
console.log(`Service unavailable. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
};
Request Queuing Implementation
Implement a queue to stay within limits:
class RateLimitedQueue {
constructor(rateLimit, burstLimit = null) {
this.rateLimit = rateLimit; // requests per second
this.burstLimit = burstLimit || rateLimit * 2;
this.tokens = this.burstLimit;
this.lastRefill = Date.now();
this.queue = [];
this.processing = false;
}
async add(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({ requestFn, resolve, reject });
this.process();
});
}
refillTokens() {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
const tokensToAdd = elapsed * this.rateLimit;
this.tokens = Math.min(this.burstLimit, this.tokens + tokensToAdd);
this.lastRefill = now;
}
async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
this.refillTokens();
if (this.tokens < 1) {
const waitTime = (1 / this.rateLimit) * 1000;
await new Promise(r => setTimeout(r, waitTime));
continue;
}
this.tokens--;
const { requestFn, resolve, reject } = this.queue.shift();
try {
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
}
}
this.processing = false;
}
}
// Usage - Orders API queue (10 req/s)
const ordersQueue = new RateLimitedQueue(10, 20);
const orders = await ordersQueue.add(() => getOrders(accessToken, options));
Security Best Practices
Credential Management
Never hardcode credentials in your source code. Use environment variables or a secrets manager:
// Bad - never do this
const AWS_ACCESS_KEY = 'AKIAIOSFODNN7EXAMPLE';
const AWS_SECRET = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY';
// Good - use environment variables
const AWS_ACCESS_KEY = process.env.AWS_ACCESS_KEY_ID;
const AWS_SECRET = process.env.AWS_SECRET_ACCESS_KEY;
// Better - use AWS Secrets Manager or similar
const { SecretsManagerClient } = require('@aws-sdk/client-secrets-manager');
const secretsClient = new SecretsManagerClient({ region: 'us-east-1' });
const getCredentials = async () => {
const response = await secretsClient.send({
Name: 'prod/sp-api/credentials'
});
return JSON.parse(response.SecretString);
};
Token Storage Requirements
SP-API requires specific security measures for token storage:
- Encrypt at rest: Use AES-256 encryption for stored tokens
- Encrypt in transit: Always use HTTPS/TLS 1.2+
- Access controls: Limit token access to specific service accounts
- Audit logging: Log all token access and refresh events
- Automatic rotation: Refresh tokens before expiration
const crypto = require('crypto');
class TokenStore {
constructor(encryptionKey) {
this.algorithm = 'aes-256-gcm';
this.key = crypto.createHash('sha256').update(encryptionKey).digest('hex').slice(0, 32);
}
encrypt(token) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(this.algorithm, Buffer.from(this.key), iv);
let encrypted = cipher.update(token, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag().toString('hex');
return {
iv: iv.toString('hex'),
encryptedData: encrypted,
authTag
};
}
decrypt(encryptedData) {
const decipher = crypto.createDecipheriv(
this.algorithm,
Buffer.from(this.key),
Buffer.from(encryptedData.iv, 'hex')
);
decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
let decrypted = decipher.update(encryptedData.encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}
IAM Least Privilege
Grant only the permissions your application needs:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SPAPIOrdersAccess",
"Effect": "Allow",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:*:*:*/prod/*/sellingpartnerapi/orders/*"
},
{
"Sid": "SPAPIInventoryAccess",
"Effect": "Allow",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:*:*:*/prod/*/sellingpartnerapi/fba/inventory/*"
}
]
}
Avoid using * wildcards in production. Scope permissions to specific endpoints.
Request Signing Security
Always validate your SigV4 implementation:
- Use HTTPS for all requests
- Include all required headers in the signature
- Ensure timestamps are within 5 minutes of AWS servers
- Rotate AWS credentials regularly
- Use IAM roles instead of long-term credentials when possible
// Validate request timestamp
const validateTimestamp = (amzDate) => {
const now = new Date();
const requestTime = new Date(amzDate);
const diff = Math.abs(now - requestTime);
// AWS rejects requests older than 5 minutes
if (diff > 5 * 60 * 1000) {
throw new Error('Request timestamp too old. Sync your server clock.');
}
};
Testing SP-API Integrations with Apidog
Why Test SP-API Integrations
Amazon SP-API integrations involve complex authentication flows, multiple endpoints, and strict rate limits. Testing helps you:
- Validate OAuth 2.0 authorization flows
- Debug SigV4 signing issues
- Test error handling for rate limits
- Mock responses for development
- Document API workflows for your team
Setting Up Apidog for SP-API Testing
Step 1: Import SP-API OpenAPI Specification
Apidog supports OpenAPI 3.0 specifications. Import Amazon’s SP-API spec:
- Download the SP-API OpenAPI spec from the Amazon repository
- In Apidog, create a new project
- Import the specification file
- Configure environment variables for credentials
Step 2: Configure Environment Variables
Set up environment-specific variables:
Base URL (Sandbox): https://sandbox.sellingpartnerapi-na.amazon.com
Base URL (Production): https://sellingpartnerapi-na.amazon.com
LWA Access Token: {{lwa_access_token}}
AWS Access Key: {{aws_access_key}}
AWS Secret Key: {{aws_secret_key}}
Region: us-east-1
Step 3: Create Pre-request Scripts
Automate SigV4 signing with Apidog’s pre-request scripts:
// Apidog pre-request script for SigV4 signing
const crypto = require('crypto');
const accessKey = apidog.variables.get('aws_access_key');
const secretKey = apidog.variables.get('aws_secret_key');
const accessToken = apidog.variables.get('lwa_access_token');
const region = apidog.variables.get('region');
const method = apidog.request.method;
const url = new URL(apidog.request.url);
const body = apidog.request.body;
// Generate SigV4 signature
const signer = new SigV4Signer(accessKey, secretKey, region);
const signedHeaders = signer.sign(method, url.href, body, {
'x-amz-access-token': accessToken
});
// Set headers for the request
apidog.request.headers = {
...apidog.request.headers,
...signedHeaders.headers
};
Step 4: Create Test Scenarios
Build test scenarios for common workflows:
// Test: Get Orders from Last 24 Hours
// 1. Exchange OAuth code for token
// 2. Call GetOrders endpoint
// 3. Validate response structure
// 4. Extract order IDs
// 5. Call GetOrderItems for each order
const ordersResponse = await apidog.send({
method: 'GET',
url: '/orders/v0/orders',
params: {
createdAfter: new Date(Date.now() - 86400000).toISOString(),
marketplaceIds: 'ATVPDKIKX0DER'
}
});
apidog.assert(ordersResponse.status === 200, 'Orders request failed');
apidog.assert(ordersResponse.data.payload.orders.length > 0, 'No orders found');
// Store order IDs for subsequent requests
const orderIds = ordersResponse.data.payload.orders.map(o => o.amazon_order_id);
apidog.variables.set('order_ids', JSON.stringify(orderIds));
Mocking SP-API Responses
Use Apidog’s smart mock feature to simulate SP-API responses during development:
// Mock response for GetOrders
{
"payload": {
"orders": [
{
"amazon_order_id": "112-{{randomNumber}}-{{randomNumber}}",
"order_status": "Unshipped",
"purchase_date": "{{now}}",
"order_total": {
"currency_code": "USD",
"amount": "{{randomFloat 10 500}}"
}
}
],
"next_token": null
}
}
This allows frontend development to proceed without hitting live Amazon endpoints.
Debugging Common Issues
Issue: SigV4 Signature Mismatch
Use Apidog’s request inspector to verify:
- All required headers are included
- Headers are sorted alphabetically
- Canonical request matches AWS expectations
- Timestamp is within valid range
Issue: OAuth Token Errors
Check token validity with a simple request:
const tokenCheck = await apidog.send({
method: 'GET',
url: '/orders/v0/orders',
params: { createdAfter: new Date().toISOString() }
});
if (tokenCheck.status === 401) {
console.log('Token expired - refresh required');
// Trigger token refresh flow
}
Automating Tests in CI/CD
Integrate Apidog tests into your CI/CD pipeline:
# GitHub Actions example
name: SP-API Integration Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Apidog Tests
uses: apidog/test-action@v1
with:
project-id: ${{ secrets.APIDOG_PROJECT_ID }}
api-key: ${{ secrets.APIDOG_API_KEY }}
environment: sandbox
- name: Notify on Failure
if: failure()
run: |
echo "SP-API tests failed - check Apidog dashboard"
Marketplace ID Reference
Keep this reference handy for common marketplaces:
| Country | Marketplace ID |
|---|---|
| United States | ATVPDKIKX0DER |
| Canada | A2EUQ1WTGCTBG2 |
| Mexico | A1AM78C64UM0Y8 |
| United Kingdom | A1F83G8C2ARO7P |
| Germany | A1PA6795UKMFR9 |
| France | A13V1IB3VIYZZH |
| Italy | APJ6JRA9NG5V4 |
| Spain | A1RKKUPIHCS9HS |
| Japan | A1VC38T7YXB528 |
| Australia | A39IBJ37TRP1C6 |
| India | A21TJRUUN4KGV |
| Brazil | A2Q3Y263D00KWC |
Troubleshooting Common Issues
Issue: 403 Unauthorized Error
Symptoms: Getting “Unauthorized” or “Access Denied” errors.
Diagnosis:
const error = await response.json();
console.error('Auth error:', error);
// Check: InvalidSignature, AccessDenied, ExpiredToken
Solutions:
- Verify AWS credentials are correct and active
- Check IAM role ARN is linked to application
- Ensure OAuth token hasn’t expired
- Confirm SigV4 signature includes correct region
- Verify
x-amz-access-tokenheader is present
Issue: 429 Rate Limit Exceeded
Symptoms: Receiving HTTP 429 responses frequently.
Solutions:
- Implement request queuing with rate limiting
- Use exponential backoff for retries
- Batch requests where possible (use next_token for pagination)
- Monitor
x-amzn-RateLimit-Limitheader proactively - Request higher limits via Seller Central for high-volume use cases
Issue: 404 Not Found
Symptoms: Valid endpoints return 404.
Solutions:
- Verify you’re using the correct regional endpoint
- Check marketplace IDs are valid for the region
- Confirm the resource (order, listing) exists
- Ensure API version in path is correct (e.g.,
/v0/,/2021-08-01/)
Issue: 400 Bad Request
Symptoms: Request fails with validation errors.
Common Causes:
- Invalid date format: Use ISO 8601 format (
2026-03-19T10:30:00Z) - Missing required parameters: Check API documentation for required fields
- Invalid marketplace ID: Use correct IDs (e.g.,
ATVPDKIKX0DERfor US) - Malformed JSON: Validate request body syntax
Solution:
// Validate dates before sending
const validateIsoDate = (dateString) => {
const date = new Date(dateString);
if (isNaN(date.getTime())) {
throw new Error('Invalid ISO 8601 date format');
}
return dateString;
};
Issue: Reports Stay in INIT_STATE
Symptoms: Report never reaches DONE status.
Solutions:
- Wait longer - some reports take 15-30 minutes
- Check report type is available for your account
- Verify date range isn’t too large (try smaller ranges)
- Poll report status every 30 seconds, not continuously
- Some report types require specific permissions
Issue: Notifications Not Arriving
Symptoms: Subscriptions created but no notifications received.
Diagnosis:
- Check subscription status via API
- Verify SNS topic policy allows Amazon SES
- Confirm endpoint URL is publicly accessible
- Check CloudWatch logs for delivery attempts
Solutions:
- Ensure SNS topic has correct resource policy
- Verify HTTPS endpoint with valid SSL certificate
- Return 200 OK within 30 seconds for all notifications
- Auto-confirm
SubscriptionConfirmationmessages
Production Deployment Checklist
Before going live:
- [ ] Register application in production Seller Central
- [ ] Configure production IAM role with least-privilege permissions
- [ ] Update all redirect URIs to production URLs
- [ ] Implement secure token storage (encrypted database)
- [ ] Add automatic token refresh logic
- [ ] Set up rate limiting and request queuing
- [ ] Configure SNS destination for notifications
- [ ] Implement comprehensive error handling
- [ ] Add logging for all API calls with request IDs
- [ ] Set up monitoring for rate limit usage
- [ ] Create runbook for common issues
- [ ] Test with multiple marketplace IDs
- [ ] Document OAuth flow for seller onboarding
- [ ] Implement retry logic with exponential backoff
- [ ] Set up alerting for authentication failures
Monitoring and Alerting
Track these metrics:
const metrics = {
apiCalls: {
total: 0,
successful: 0,
failed: 0,
rateLimited: 0
},
rateLimitUsage: {
orders: { current: 0, limit: 10 },
inventory: { current: 0, limit: 2 },
listings: { current: 0, limit: 10 }
},
oauthTokens: {
active: 0,
expiring_soon: 0,
refresh_failures: 0
},
notifications: {
received: 0,
processed: 0,
failed: 0
},
reports: {
pending: 0,
completed: 0,
failed: 0
}
};
// Alert on high failure rate
const failureRate = metrics.apiCalls.failed / metrics.apiCalls.total;
if (failureRate > 0.05) { // More than 5% failure rate
sendAlert('SP-API failure rate above 5%');
}
// Alert on rate limit approaching
for (const [endpoint, usage] of Object.entries(metrics.rateLimitUsage)) {
if (usage.current / usage.limit > 0.8) {
sendAlert(`${endpoint} rate limit at 80% capacity`);
}
}
Real-World Use Cases
Multi-Marketplace Inventory Sync
A global electronics seller uses SP-API to synchronize inventory across 12 marketplaces:
- Challenge: Manual inventory updates led to overselling in EU marketplaces
- Solution: Central inventory system with SP-API webhooks and rate-limited queues
- Result: Zero overselling incidents, 25 hours/week time savings
Implementation flow:
- SNS notification triggers on
InventoryLevelsevent - Central system calculates new quantities
- Rate-limited API calls update each marketplace
- Confirmation logged to audit database
Automated Order Fulfillment
A third-party logistics provider automates order processing:
- Challenge: 200+ daily orders required manual data entry across systems
- Solution: SP-API integration with warehouse management system
- Result: Orders auto-routed to warehouse within 2 minutes of placement
Key integration points:
- Webhook listens for
OrderStatusChangeevents - Order details sent to WMS via REST API
- Tracking number returned and updated via SP-API
- Customer receives automatic shipping notification
Analytics Dashboard
A seller analytics tool aggregates data across 500+ seller accounts:
- Challenge: Sellers lacked unified reporting across marketplaces
- Solution: OAuth-based multi-seller data aggregation with token management
- Result: Real-time dashboard with sales, inventory, and advertising metrics
Data collected via SP-API:
- Orders and order items across all marketplaces
- FBA inventory levels and inbound shipments
- Listing performance and pricing data
- Brand analytics and search term reports
Conclusion
Amazon SP-API provides comprehensive access to seller central functionality with improved security and performance over legacy MWS. Key takeaways:
- OAuth 2.0 + IAM roles require careful credential management and automatic token refresh
- AWS SigV4 signing is mandatory for all requests - use SDK or proven libraries
- Rate limits vary by endpoint (0.5 to 100 req/s) and require proactive monitoring
- Notifications via SNS enable real-time order and inventory synchronization
- Proper error handling and retry logic are essential for production reliability
- Apidog streamlines API testing and team collaboration for SP-API integrations
FAQ Section
What is Amazon SP-API?
Amazon Selling Partner API (SP-API) is a REST-based API that provides programmatic access to seller central data including orders, inventory, listings, and reports. It replaced the older MWS system with improved security through OAuth 2.0 and AWS SigV4 signing.
How do I get Amazon SP-API credentials?
Register your application in Seller Central under Apps and Services > Develop Apps. You’ll receive a Client ID, Client Secret, and Application ID. You’ll also need to create an IAM role in AWS and link it to your application.
Is Amazon SP-API free to use?
Yes, SP-API access is free for registered Amazon sellers. However, rate limits apply and vary by endpoint (0.5 to 100 requests per second). Higher limits require approval from Amazon for specific high-volume use cases.
What authentication does SP-API use?
SP-API uses OAuth 2.0 for authorization combined with AWS IAM roles for access control. All API requests require AWS Signature Version 4 (SigV4) signing with temporary credentials obtained through the OAuth flow.
How do I handle SP-API rate limits?
Implement request queuing to stay within per-endpoint limits. Monitor the x-amzn-RateLimit-Limit header to track usage. Use exponential backoff when receiving HTTP 429 (Too Many Requests) responses. Different endpoints have different limits.
Can I test SP-API without a live seller account?
Yes. Amazon provides a sandbox environment for SP-API development. Register your application in sandbox mode to test integrations without affecting live seller data. Note that not all endpoints are available in sandbox.
How do webhooks work with SP-API?
SP-API uses Amazon SNS for notifications. Create a subscription for specific event types (orders, inventory, etc.), configure an SNS topic, and implement an HTTPS endpoint to receive notifications. Auto-confirm subscription messages.
What happens when OAuth token expires?
LWA access tokens expire after 1 hour. Use the refresh token to obtain a new access token before expiration. Implement automatic token refresh in your middleware to prevent authentication failures during API calls.
How do I migrate from MWS to SP-API?
MWS retirement is scheduled for December 2025. Migration requires: updating authentication from MWS tokens to OAuth 2.0, implementing SigV4 signing, updating endpoint URLs, and modifying request/response parsing from XML to JSON.
Why am I getting 403 Unauthorized errors?
Common causes include: expired OAuth token, incorrect IAM role configuration, invalid SigV4 signature, missing x-amz-access-token header, or IAM role not linked to the application. Check error response details for specific error codes.



