How to Integrate Amazon SP API: Step-by-Step Tutorial

Complete Amazon SP-API integration guide: OAuth 2.0 setup, AWS SigV4 signing, orders/inventory/listings endpoints, webhooks, and production deployment.

Ashley Innocent

Ashley Innocent

20 March 2026

How to Integrate Amazon SP API: Step-by-Step Tutorial

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.

💡
Apidog simplifies API integration testing. Test your SP-API endpoints, validate OAuth flows, inspect request signatures, and debug authentication issues in one workspace. Import API specifications, mock responses, and share test scenarios with your team.
button

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:

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:

  1. AWS SigV4 signature
  2. Access token from OAuth flow
  3. Proper IAM role permissions
  4. 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:

  1. Visit the Amazon Developer Central
  2. Sign in with your Amazon account (must have Seller Central access)
  3. Navigate to Selling Partner API in the dashboard
  4. Accept the Developer Agreement

Step 2: Register Your Application

Create an application profile in Seller Central:

  1. Log into Seller Central
  2. Navigate to Apps and Services > Develop Apps
  3. Click Add New App
  4. Fill in application details:

After submission, you’ll receive:

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:

  1. Log into AWS IAM Console
  2. Navigate to Roles > Create Role
  3. Select Another AWS account as trusted entity
  4. Enter Amazon’s account ID for your region:

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.

Connect your IAM role to the SP-API application:

  1. Return to Seller Central > Develop Apps
  2. Select your application
  3. Click Edit > IAM Role ARN
  4. Enter your IAM role ARN
  5. 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:

  1. FBA shipments - Send inventory to Amazon warehouses
  2. MFN orders - Inventory decreases automatically when orders ship
  3. 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:

  1. Encrypt at rest: Use AES-256 encryption for stored tokens
  2. Encrypt in transit: Always use HTTPS/TLS 1.2+
  3. Access controls: Limit token access to specific service accounts
  4. Audit logging: Log all token access and refresh events
  5. 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:

  1. Use HTTPS for all requests
  2. Include all required headers in the signature
  3. Ensure timestamps are within 5 minutes of AWS servers
  4. Rotate AWS credentials regularly
  5. 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:

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:

  1. Download the SP-API OpenAPI spec from the Amazon repository
  2. In Apidog, create a new project
  3. Import the specification file
  4. 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:

  1. All required headers are included
  2. Headers are sorted alphabetically
  3. Canonical request matches AWS expectations
  4. 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:

  1. Verify AWS credentials are correct and active
  2. Check IAM role ARN is linked to application
  3. Ensure OAuth token hasn’t expired
  4. Confirm SigV4 signature includes correct region
  5. Verify x-amz-access-token header is present

Issue: 429 Rate Limit Exceeded

Symptoms: Receiving HTTP 429 responses frequently.

Solutions:

  1. Implement request queuing with rate limiting
  2. Use exponential backoff for retries
  3. Batch requests where possible (use next_token for pagination)
  4. Monitor x-amzn-RateLimit-Limit header proactively
  5. Request higher limits via Seller Central for high-volume use cases

Issue: 404 Not Found

Symptoms: Valid endpoints return 404.

Solutions:

  1. Verify you’re using the correct regional endpoint
  2. Check marketplace IDs are valid for the region
  3. Confirm the resource (order, listing) exists
  4. 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:

  1. Invalid date format: Use ISO 8601 format (2026-03-19T10:30:00Z)
  2. Missing required parameters: Check API documentation for required fields
  3. Invalid marketplace ID: Use correct IDs (e.g., ATVPDKIKX0DER for US)
  4. 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:

  1. Wait longer - some reports take 15-30 minutes
  2. Check report type is available for your account
  3. Verify date range isn’t too large (try smaller ranges)
  4. Poll report status every 30 seconds, not continuously
  5. Some report types require specific permissions

Issue: Notifications Not Arriving

Symptoms: Subscriptions created but no notifications received.

Diagnosis:

  1. Check subscription status via API
  2. Verify SNS topic policy allows Amazon SES
  3. Confirm endpoint URL is publicly accessible
  4. Check CloudWatch logs for delivery attempts

Solutions:

  1. Ensure SNS topic has correct resource policy
  2. Verify HTTPS endpoint with valid SSL certificate
  3. Return 200 OK within 30 seconds for all notifications
  4. Auto-confirm SubscriptionConfirmation messages

Production Deployment Checklist

Before going live:

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:

Implementation flow:

  1. SNS notification triggers on InventoryLevels event
  2. Central system calculates new quantities
  3. Rate-limited API calls update each marketplace
  4. Confirmation logged to audit database

Automated Order Fulfillment

A third-party logistics provider automates order processing:

Key integration points:

Analytics Dashboard

A seller analytics tool aggregates data across 500+ seller accounts:

Data collected via SP-API:

Conclusion

Amazon SP-API provides comprehensive access to seller central functionality with improved security and performance over legacy MWS. Key takeaways:

button

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.

Explore more

How to Document APIs for Internal and External Stakeholders: A Complete Guide

How to Document APIs for Internal and External Stakeholders: A Complete Guide

Discover how to document APIs for internal and external stakeholders. This guide explores best practices, real-world examples, and tools like Apidog to streamline and optimize your API documentation process.

20 March 2026

API Adoption: Strategies, Benefits, and Best Practices

API Adoption: Strategies, Benefits, and Best Practices

API adoption is key to unlocking business agility, integration, and innovation. This guide covers what API adoption is, why it matters, strategies for success, common challenges, and real-world examples—plus how Apidog can streamline your API adoption journey.

20 March 2026

How to Use Etsy API: Complete Integration Guide (2026)

How to Use Etsy API: Complete Integration Guide (2026)

Master Etsy API integration with this complete guide. Learn OAuth 2.0 authentication, shop management, listings, orders, webhooks, and production deployment.

20 March 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs