Amazon shut down MWS (Marketplace Web Services) on March 31, 2024. If you’re reading this because an old integration broke, or because you’re planning a new Amazon integration and want to understand the landscape — this guide covers everything.
We’ll look at what changed architecturally, what’s better and worse in SP-API, and walk through the migration path for the most common MWS integrations.
What Was MWS?
Amazon MWS (Marketplace Web Services) was Amazon’s seller API from 2009 to 2024. It used:
- Authentication: AWS query-parameter signatures (not IAM roles)
- Format: XML-based requests and responses
- Access model: Developer keys + seller authorization tokens
- Endpoints: US-specific endpoint:
https://mws.amazonservices.com
MWS was functional but had significant problems: XML parsing was painful, the authentication model was non-standard, documentation was poor, and the API surface was fragmented across dozens of separate “sections” (Orders, Products, Reports, FBA Inventory, etc.).
Official deprecation notice: MWS Migration Guide
What Is SP-API?
Amazon Selling Partner API (SP-API), launched in 2020, replaces MWS completely. Key differences:
| Feature | MWS | SP-API |
|---|---|---|
| Auth method | MD5 query signing | AWS IAM + LWA OAuth 2.0 |
| Response format | XML | JSON |
| Regional endpoints | US-centric | Regional (NA, EU, FE) |
| Rate limiting | Per-API quotas | Burst + restore (tokens) |
| Security model | Shared developer secret | Per-app IAM roles |
| Documentation | Poor | Much better (OpenAPI specs available) |
| Webhooks/notifications | Not supported | Notifications API |
| Restricted data | Embedded in responses | Requires RDT (Restricted Data Tokens) |
Architecture Differences
Authentication: The Biggest Change
MWS auth was relatively simple — you included your developer key, seller auth token, and a query signature in every request.
SP-API auth has two layers:
- LWA (Login with Amazon) OAuth 2.0 — gets a short-lived access token per seller
- AWS SigV4 — signs every API request using IAM credentials
# MWS request (old, simple but insecure)
GET https://mws.amazonservices.com/Orders/2013-09-01
?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE
&SellerId=A2SUAM1J3NJ3ZN
&Timestamp=2024-01-01T00:00:00Z
&SignatureVersion=2
&SignatureMethod=HmacSHA256
&Signature=...
# SP-API request (new — LWA token + SigV4)
GET https://sellingpartnerapi-na.amazon.com/orders/v0/orders
Authorization: AWS4-HMAC-SHA256 Credential=AKID.../aws4_request, ...
x-amz-access-token: Atza|eyJ... (LWA token)
x-amz-date: 20240101T000000Z
Regional Endpoints
SP-API uses three regional endpoints instead of MWS’s US-centric approach:
| Region | Endpoint | Marketplaces |
|---|---|---|
| North America | sellingpartnerapi-na.amazon.com | US, Canada, Mexico, Brazil |
| Europe | sellingpartnerapi-eu.amazon.com | UK, Germany, France, Italy, Spain, etc. |
| Far East | sellingpartnerapi-fe.amazon.com | Japan, Australia, Singapore |
If you sell in multiple regions, you need separate auth flows and API calls per region.
Setting Up SP-API From Scratch
Step 1: Create an AWS Account and IAM User
- Create an AWS account at aws.amazon.com
- AWS Console → IAM → Users → Create user
- Attach policy: AmazonSellingPartnerAPIRole (or create a custom policy)
- Create access key → save the Access Key ID and Secret Access Key
Step 2: Create IAM Role for SP-API
SP-API requires an IAM role with a trust relationship to Amazon’s SP-API service.
- AWS Console → IAM → Roles → Create role
- Trusted entity type: Custom trust policy
- Trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "sellingpartner.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
- Attach permissions:
AmazonSellingPartnerAPIRole - Copy the Role ARN (you’ll need it when registering your app)
Step 3: Register Your SP-API Application
- Log into Seller Central with a Professional seller account
- Apps and Services → Develop Apps → Add new app client
- Fill in:
- App name
- IAM ARN: your role ARN from Step 2
- OAuth redirect URI: your callback URL
- Submit for review (self-authorization for private apps is immediate)
Step 4: Get LWA OAuth Credentials
After app registration:
- Developer Central → your app → LWA credentials
- Copy: Client ID (
amzn1.application-oa2-client.xxx) and Client Secret
Step 5: Get Seller Authorization
For self-use (your own seller account):
- In Seller Central → Apps and Services → Manage Your Apps
- Authorize your own app → grants you a Refresh Token for your seller account
For third-party sellers (building a multi-tenant integration):
- Implement the OAuth flow so sellers can authorize your app through Seller Central
Step 6: Exchange Tokens and Make API Calls
// 1. Exchange refresh token for access token (expires in 1 hour)
async function getLWAToken(refreshToken) {
const response = await fetch('https://api.amazon.com/auth/o2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: process.env.LWA_CLIENT_ID,
client_secret: process.env.LWA_CLIENT_SECRET,
}),
});
const { access_token } = await response.json();
return access_token;
}
// 2. Sign request with AWS SigV4
import { SignatureV4 } from '@aws-sdk/signature-v4';
import { Sha256 } from '@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,
});
// 3. Make signed API call
async function getOrders(lwaToken) {
const url = new URL('https://sellingpartnerapi-na.amazon.com/orders/v0/orders');
url.searchParams.set('MarketplaceIds', 'ATVPDKIKX0DER'); // US
url.searchParams.set('CreatedAfter', '2024-01-01T00:00:00Z');
const request = {
method: 'GET',
hostname: 'sellingpartnerapi-na.amazon.com',
path: '/orders/v0/orders?' + url.searchParams.toString(),
headers: {
host: 'sellingpartnerapi-na.amazon.com',
'x-amz-access-token': lwaToken,
},
};
const signed = await signer.sign(request);
const response = await fetch(`https://${signed.hostname}${signed.path}`, {
headers: signed.headers,
});
return response.json();
}
Reference: SP-API Getting Started
Migrating Common MWS Integrations
Orders API
| MWS | SP-API |
|---|---|
ListOrders | GET /orders/v0/orders |
GetOrder | GET /orders/v0/orders/{orderId} |
ListOrderItems | GET /orders/v0/orders/{orderId}/orderItems |
MWS (old):
<!-- MWS XML request — thankfully this is dead -->
<ListOrdersRequest>
<SellerId>YOUR_SELLER_ID</SellerId>
<CreatedAfter>2024-01-01T00:00:00Z</CreatedAfter>
<MarketplaceId>ATVPDKIKX0DER</MarketplaceId>
</ListOrdersRequest>
SP-API (new):
// Clean JSON, proper REST
const response = await fetch(
`https://sellingpartnerapi-na.amazon.com/orders/v0/orders?MarketplaceIds=ATVPDKIKX0DER&CreatedAfter=2024-01-01T00:00:00Z`,
{ headers: signedHeaders }
);
const { payload: { Orders } } = await response.json();
Inventory API
FBA inventory is now split between two SP-API sections:
| Purpose | SP-API Endpoint |
|---|---|
| Real-time FBA inventory | GET /fba/inventory/v1/summaries |
| Reserved inventory breakdown | GET /fba/inventory/v1/summaries?details=true |
| FBA inbound shipments | POST /inbound/fba/2024-03-20/inboundPlans (new 2024 API) |
// Get FBA inventory summary
const inventoryResponse = await fetch(
`https://sellingpartnerapi-na.amazon.com/fba/inventory/v1/summaries?granularityType=Marketplace&granularityId=ATVPDKIKX0DER&marketplaceIds=ATVPDKIKX0DER`,
{ headers: signedHeaders }
);
const { payload: { inventorySummaries } } = await inventoryResponse.json();
// Each item returns:
// { asin, fnSku, sellerSku, condition, inventoryDetails: { fulfillableQuantity, ... } }
Reference: FBA Inventory API v1
Reports API
The MWS Reports section had around 100 report types. SP-API’s Reports API has them all, but the request model is now asynchronous:
// Step 1: Request a report
async function requestReport(reportType, lwaToken) {
const response = await fetch(
'https://sellingpartnerapi-na.amazon.com/reports/2021-06-30/reports',
{
method: 'POST',
headers: { ...signedHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({
reportType, // e.g., 'GET_FLAT_FILE_ORDERS_DATA_BY_ORDER_DATE_GENERAL'
marketplaceIds: ['ATVPDKIKX0DER'],
dataStartTime: '2024-01-01T00:00:00Z',
dataEndTime: '2024-01-31T23:59:59Z',
}),
}
);
const { reportId } = await response.json();
return reportId;
}
// Step 2: Poll for completion
async function waitForReport(reportId) {
while (true) {
const status = await fetch(
`https://sellingpartnerapi-na.amazon.com/reports/2021-06-30/reports/${reportId}`,
{ headers: signedHeaders }
);
const { processingStatus, reportDocumentId } = await status.json();
if (processingStatus === 'DONE') return reportDocumentId;
if (processingStatus === 'FATAL') throw new Error('Report generation failed');
await new Promise(r => setTimeout(r, 30000)); // wait 30s before retry
}
}
// Step 3: Download the report document
async function downloadReport(reportDocumentId) {
const docResponse = await fetch(
`https://sellingpartnerapi-na.amazon.com/reports/2021-06-30/documents/${reportDocumentId}`,
{ headers: signedHeaders }
);
const { url, compressionAlgorithm } = await docResponse.json();
// url is a pre-signed S3 URL — download directly (no auth headers needed)
const fileResponse = await fetch(url);
const content = await fileResponse.text();
// If compressionAlgorithm === 'GZIP', decompress first
return content;
}
Reference: Reports API 2021-06-30
Rate Limiting in SP-API
SP-API uses a token bucket rate limiting model. Each endpoint has:
- Rate (requests/sec): How fast the bucket refills
- Burst: Maximum requests that can fire at once
Common limits:
| API | Rate | Burst |
|---|---|---|
| Orders — ListOrders | 0.0167 req/s | 20 |
| Orders — GetOrder | 0.5 req/s | 30 |
| FBA Inventory | 2 req/s | 2 |
| Reports — CreateReport | 0.0167 req/s | 15 |
| Catalog Items | 2 req/s | 2 |
SP-API returns x-amzn-RateLimit-Limit and Retry-After headers. Always implement exponential backoff:
async function apiCallWithRetry(url, options, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '5');
const wait = retryAfter * 1000 * Math.pow(2, attempt);
console.log(`Rate limited. Waiting ${wait}ms...`);
await new Promise(r => setTimeout(r, wait));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
Reference: SP-API Usage Plans and Rate Limits
Restricted Data Tokens (PII Access)
SP-API introduced Restricted Data Tokens (RDTs) for accessing Personally Identifiable Information — buyer names, addresses, phone numbers. In MWS, this data was returned directly. In SP-API, you must explicitly request a token for PII access.
// Request an RDT for order PII
async function getRestrictedDataToken(orderIds) {
const response = await fetch(
'https://sellingpartnerapi-na.amazon.com/tokens/2021-03-01/restrictedDataToken',
{
method: 'POST',
headers: { ...signedHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({
restrictedResources: orderIds.map(id => ({
method: 'GET',
path: `/orders/v0/orders/${id}/address`,
dataElements: ['buyerInfo', 'shippingAddress'],
})),
}),
}
);
const { restrictedDataToken, expiresIn } = await response.json();
return restrictedDataToken;
}
// Use the RDT to get the shipping address
async function getOrderAddress(orderId, rdt) {
const response = await fetch(
`https://sellingpartnerapi-na.amazon.com/orders/v0/orders/${orderId}/address`,
{
headers: {
...signedHeaders,
'x-amz-access-token': rdt, // Use RDT instead of normal LWA token
},
}
);
return response.json();
}
Reference: Tokens API for Restricted Data Tokens
Production Checklist for SP-API
□ IAM role created with correct trust policy
□ SP-API application registered in Developer Central
□ LWA Client ID and Secret stored in environment variables
□ Access Key / Secret Key stored in secrets manager (never hardcoded)
□ Refresh token exchange working correctly
□ SigV4 signing implemented for all requests
□ Rate limit handling with exponential backoff
□ Token refresh logic (LWA tokens expire in 1 hour)
□ Logging for all API requests/responses
□ Error handling for 5xx responses (Amazon servers do go down)
□ Regional endpoint configured correctly for your marketplace
□ GDPR: RDT used for any PII fields (shipping address, buyer name)
□ Notifications API webhook configured for real-time order events
If you’re migrating from MWS or building a new SP-API integration, the authentication layer alone takes significant setup time. We’ve built SP-API integrations for inventory sync, order management, and analytics pipelines. Book a call to discuss your integration requirements.