Amazon 13 min read

Amazon SP-API vs MWS: Complete Migration Guide for Sellers and Developers

MWS is fully decommissioned. This guide covers what changed from MWS to SP-API, how to migrate your existing integrations, and what SP-API does better.

A
Ashwani Bhasin
·

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:

FeatureMWSSP-API
Auth methodMD5 query signingAWS IAM + LWA OAuth 2.0
Response formatXMLJSON
Regional endpointsUS-centricRegional (NA, EU, FE)
Rate limitingPer-API quotasBurst + restore (tokens)
Security modelShared developer secretPer-app IAM roles
DocumentationPoorMuch better (OpenAPI specs available)
Webhooks/notificationsNot supportedNotifications API
Restricted dataEmbedded in responsesRequires 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:

  1. LWA (Login with Amazon) OAuth 2.0 — gets a short-lived access token per seller
  2. 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:

RegionEndpointMarketplaces
North Americasellingpartnerapi-na.amazon.comUS, Canada, Mexico, Brazil
Europesellingpartnerapi-eu.amazon.comUK, Germany, France, Italy, Spain, etc.
Far Eastsellingpartnerapi-fe.amazon.comJapan, 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

  1. Create an AWS account at aws.amazon.com
  2. AWS Console → IAM → Users → Create user
  3. Attach policy: AmazonSellingPartnerAPIRole (or create a custom policy)
  4. 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.

  1. AWS Console → IAM → Roles → Create role
  2. Trusted entity type: Custom trust policy
  3. Trust policy:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "sellingpartner.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
  1. Attach permissions: AmazonSellingPartnerAPIRole
  2. Copy the Role ARN (you’ll need it when registering your app)

Step 3: Register Your SP-API Application

  1. Log into Seller Central with a Professional seller account
  2. Apps and Services → Develop Apps → Add new app client
  3. Fill in:
    • App name
    • IAM ARN: your role ARN from Step 2
    • OAuth redirect URI: your callback URL
  4. Submit for review (self-authorization for private apps is immediate)

Step 4: Get LWA OAuth Credentials

After app registration:

  1. Developer Central → your app → LWA credentials
  2. Copy: Client ID (amzn1.application-oa2-client.xxx) and Client Secret

Step 5: Get Seller Authorization

For self-use (your own seller account):

  1. In Seller Central → Apps and Services → Manage Your Apps
  2. 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

MWSSP-API
ListOrdersGET /orders/v0/orders
GetOrderGET /orders/v0/orders/{orderId}
ListOrderItemsGET /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:

PurposeSP-API Endpoint
Real-time FBA inventoryGET /fba/inventory/v1/summaries
Reserved inventory breakdownGET /fba/inventory/v1/summaries?details=true
FBA inbound shipmentsPOST /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:

APIRateBurst
Orders — ListOrders0.0167 req/s20
Orders — GetOrder0.5 req/s30
FBA Inventory2 req/s2
Reports — CreateReport0.0167 req/s15
Catalog Items2 req/s2

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.

#amazon#sp-api#mws#selling-partner-api#migration#aws#oauth

Share this article

Want This Implemented Correctly?

Let our team apply these concepts to your specific setup — with QA validation and 30 days of support.