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.
Share this article