DEV Community

Cover image for Amazon SP API Integration: Schritt-für-Schritt Anleitung
Emre Demir
Emre Demir

Posted on • Originally published at apidog.com

Amazon SP API Integration: Schritt-für-Schritt Anleitung

TL;DR

Die Amazon Selling Partner API (SP-API) ist eine REST-basierte API, die programmatischen Zugriff auf Verkäuferdaten für Bestellungen, Lagerbestand, Angebote und Fulfillment ermöglicht. Sie verwendet OAuth 2.0-Authentifizierung mit IAM-Rollen, erfordert AWS SigV4-Signierung und erzwingt Ratenbegrenzungen, die je nach Endpunkt variieren (0,1 bis 100 Anfragen pro Sekunde). Dieser Leitfaden behandelt die Kontoeinrichtung, Authentifizierung, Kern-Endpunkte, Webhook-Abonnements und Strategien für die Produktionsbereitstellung.

Teste Apidog noch heute

Einführung

Amazon verarbeitet über 350 Millionen Produkte auf mehr als 200 Marktplätzen weltweit. Entwickler, die E-Commerce-Tools, Bestandsverwaltungssysteme oder Analyseplattformen bauen, kommen an einer robusten Amazon SP-API-Integration nicht vorbei.

Verkäufer verlieren oft 20-30 Stunden pro Woche durch manuelle Dateneingabe bei Bestellungen, Lagerbestand und Angeboten. Mit einer durchdachten SP-API-Integration automatisierst du die Bestellsynchronisierung, Bestandsaktualisierung und Angebotsverwaltung über alle Marktplätze hinweg.

In dieser Anleitung lernst du Schritt für Schritt: IAM-Rollen einrichten, OAuth 2.0-Autorisierung, AWS SigV4-Signierung, Bestands-/Bestellmanagement, Webhook-Abonnements und Troubleshooting. Am Ende steht eine produktionsreife Amazon-Integration.

💡 Tipp: Apidog vereinfacht das Testen der API-Integration. Teste SP-API-Endpunkte, validiere OAuth-Flows, prüfe Signaturen und debugge Authentifizierungsprobleme in einem Workspace. Importiere Spezifikationen, modelliere Antworten und teile Tests mit deinem Team.

Was ist die Amazon SP-API?

Die Amazon Selling Partner API (SP-API) ist eine REST-basierte API für den Zugriff auf Seller Central-Daten. Sie ersetzt den alten Marketplace Web Service (MWS) und bringt mehr Sicherheit, Performance und Features.

Wichtige Funktionen

  • Abruf und Statusaktualisierung von Bestellungen
  • Bestandsmanagement über alle Marktplätze
  • Erstellung, Änderung und Löschung von Angeboten
  • FBA-Sendungsmanagement
  • Produktpreise & Wettbewerbsanalyse
  • Berichts- und Analysedaten
  • Verwaltung von A+ Content
  • Markenanalyse und Werbedaten

SP-API vs. MWS Vergleich

Funktion SP-API MWS (veraltet)
Architektur RESTful JSON XML-basiert
Authentifizierung OAuth 2.0 + IAM MWS Auth Token
Sicherheit AWS SigV4-Signierung Einfache Tokens
Ratenbegrenzungen Dynamisch pro Endpunkt Feste Kontingente
Marktplätze Einheitliche Endpunkte Regionsspezifisch
Status Aktuell Veraltet (Dez. 2025)

Empfehlung: Migriere bestehende MWS-Integrationen rasch auf die SP-API. MWS wird mit Dezember 2025 eingestellt.

Übersicht über die API-Architektur

Amazon setzt auf eine regionale Architektur mit zentraler Autorisierung:

https://sellingpartnerapi-na.amazon.com (Nordamerika)
https://sellingpartnerapi-eu.amazon.com (Europa)
https://sellingpartnerapi-fe.amazon.com (Fernost)
Enter fullscreen mode Exit fullscreen mode

Jede Anfrage benötigt:

  1. AWS SigV4-Signatur
  2. Zugangstoken aus dem OAuth-Flow
  3. Richtige IAM-Rollenberechtigungen
  4. Anforderungs-ID zu Nachverfolgung

Unterstützte Marktplätze

Region Marktplätze API-Endpunkt
Nordamerika US, CA, MX sellingpartnerapi-na.amazon.com
Europa UK, DE, FR, IT, ES, NL, SE, PL, TR, EG, IN, AE, SA sellingpartnerapi-eu.amazon.com
Fernost JP, AU, SG, BR sellingpartnerapi-fe.amazon.com

Erste Schritte: Konto- und IAM-Einrichtung

Schritt 1: Amazon Developer-Konto anlegen

  1. Gehe zu Amazon Developer Central
  2. Mit Amazon-Konto anmelden (muss Seller Central Zugriff haben)
  3. Im Dashboard: Selling Partner API auswählen
  4. Entwicklervereinbarung akzeptieren

Schritt 2: Anwendung in Seller Central registrieren

  1. In Seller Central einloggen
  2. Apps und Dienste > Apps entwickeln
  3. Neue App hinzufügen
  4. Details eintragen:
    • Name: Aussagekräftig wählen
    • Typ: „Selbst entwickelt“ oder „Drittanbieter“
    • Anwendungsfall: Zweck der Integration
    • Umleitungs-URI: HTTPS OAuth-Callback-URL

Nach dem Absenden erhältst du:

  • Anwendungs-ID
  • Client-ID
  • Client-Geheimnis

Best Practice: Credentials immer als Umgebungsvariablen speichern:

# .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"
Enter fullscreen mode Exit fullscreen mode

Schritt 3: IAM-Rolle für SP-API erstellen

  1. In der AWS IAM-Konsole anmelden
  2. Rollen > Rolle erstellen
  3. Anderes AWS-Konto als vertrauenswürdige Entität wählen
  4. Konto-ID für Region angeben:
    • Nordamerika: 906394416454
    • Europa: 336853085554
    • Fernost: 774466381866

Schritt 4: IAM-Richtlinie zuweisen

Policy für die IAM-Rolle:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "execute-api:Invoke"
      ],
      "Resource": [
        "arn:aws:execute-api:*:*:*/prod/*/sellingpartnerapi/*"
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Rolle sinnvoll benennen (z.B. SellingPartnerApiRole) und ARN notieren.

Schritt 5: IAM-Rolle mit Anwendung verknüpfen

  1. In Seller Central: Apps entwickeln auswählen
  2. Anwendung auswählen, Bearbeiten > IAM Role ARN
  3. Rollen-ARN eintragen, Änderungen speichern
  4. Nach wenigen Minuten Status „Verknüpft“

OAuth 2.0 Authentifizierungsflow

Ablauf

  1. Verkäufer klickt in der App auf „Autorisieren“
  2. Redirect zur Amazon-Autorisierungs-URL
  3. Verkäufer gewährt Berechtigungen
  4. Amazon gibt Autorisierungscode zurück
  5. App tauscht Code gegen LWA-Token (Login with Amazon)
  6. LWA-Token gegen SP-API-Zugriffstoken eintauschen
  7. Zugriffstoken für signierte API-Calls nutzen
  8. Token nach Ablauf (1h) refreshen

Schritt 6: Autorisierungs-URL generieren

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,
    scope: 'sellingpartnerapi::notifications'
  });
  return `${baseUrl}?${params.toString()}`;
};

const authUrl = generateAuthUrl(
  process.env.AMAZON_CLIENT_ID,
  'https://your-app.com/callback',
  crypto.randomBytes(16).toString('hex')
);
console.log(`Redirect user to: ${authUrl}`);
Enter fullscreen mode Exit fullscreen mode

Wichtige OAuth-Scopes

Scope Beschreibung Anwendungsfall
sellingpartnerapi::notifications Benachrichtigungen erhalten Webhook-Abos
sellingpartnerapi::migration Von MWS migrieren Legacy-Migration

Schritt 7: Autorisierungscode gegen LWA-Token tauschen

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,
    token_type: data.token_type
  };
};

// Express-Callback-Handler
app.get('/callback', async (req, res) => {
  const { spapi_oauth_code, state } = req.query;
  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');
    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');
  }
});
Enter fullscreen mode Exit fullscreen mode

Schritt 8: LWA-Token gegen temporäre AWS-Credentials tauschen

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();
  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;
};
Enter fullscreen mode Exit fullscreen mode

Schritt 9: Token Refresh implementieren

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,
    expires_in: data.expires_in
  };
};

// Middleware für gültiges Token
const ensureValidToken = async (sellerId) => {
  const seller = await db.sellers.findById(sellerId);
  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;
};
Enter fullscreen mode Exit fullscreen mode

AWS SigV4 Anforderungssignierung

Manuelle SigV4-Signierung

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();
    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');
    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');
    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');
    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);
  }
}

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
});
Enter fullscreen mode Exit fullscreen mode

AWS SDK für SigV4 bevorzugen

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();
};
Enter fullscreen mode Exit fullscreen mode

Bestellungen API

Bestellungen abrufen

const getOrders = async (accessToken, options = {}) => {
  const params = new URLSearchParams({
    createdAfter: options.createdAfter,
    createdBefore: options.createdBefore,
    orderStatuses: options.orderStatuses?.join(',') || '',
    marketplaceIds: options.marketplaceIds?.join(',') || ['ATVPDKIKX0DER'],
    maxResultsPerPage: options.maxResultsPerPage || 100
  });
  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);
};

const orders = await getOrders(accessToken, {
  createdAfter: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
  orderStatuses: ['Unshipped', 'PartiallyShipped'],
  marketplaceIds: ['ATVPDKIKX0DER']
});
Enter fullscreen mode Exit fullscreen mode

Bestellpositionen abrufen

const getOrderItems = async (accessToken, orderId) => {
  const endpoint = `https://sellingpartnerapi-na.amazon.com/orders/v0/orders/${orderId}/orderItems`;
  return makeSpApiRequest('GET', endpoint, accessToken);
};

const orderItems = await getOrderItems(accessToken, '112-1234567-1234567');
Enter fullscreen mode Exit fullscreen mode

Versandstatus aktualisieren

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,
      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);
};

await confirmShipment(accessToken, '112-1234567-1234567', {
  carrierCode: 'USPS',
  trackingNumber: '9400111899223456789012',
  items: [
    { orderItemId: '12345678901234', quantity: 2 }
  ]
});
Enter fullscreen mode Exit fullscreen mode

Gängige Carrier-Codes

Spediteur Spediteur-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

Bestands-API

Bestandsübersichten abrufen

const getInventorySummaries = async (accessToken, options = {}) => {
  const params = new URLSearchParams({
    granularityType: options.granularityType || 'Marketplace',
    granularityId: options.granularityId || 'ATVPDKIKX0DER',
    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);
};

const inventory = await getInventorySummaries(accessToken, {
  granularityId: 'ATVPDKIKX0DER',
  sellerSkus: ['MYSKU-001', 'MYSKU-002']
});
Enter fullscreen mode Exit fullscreen mode

Bestand aktualisieren

Bestandsänderungen laufen über FBA-Sendungen, MFN-Bestellungen oder Angebots-Updates. Beispiel für FBA-Inbound-Shipment-Plan:

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);
};
Enter fullscreen mode Exit fullscreen mode

Angebots-API

Angebote abrufen

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);
};

const listings = await getListings(accessToken, {
  identifiers: ['B08N5WRWNW', 'B09JQKJXYZ'],
  itemTypes: ['ASIN']
});
Enter fullscreen mode Exit fullscreen mode

Angebot erstellen/aktualisieren

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);
};
Enter fullscreen mode Exit fullscreen mode

Angebot löschen

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);
};
Enter fullscreen mode Exit fullscreen mode

Berichts-API

Bericht erstellen

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);
};
Enter fullscreen mode Exit fullscreen mode

Berichtsdokument abrufen

const getReportDocument = async (accessToken, reportId) => {
  const endpoint = `https://sellingpartnerapi-na.amazon.com/reports/2021-06-30/reports/${reportId}/document`;
  return makeSpApiRequest('GET', endpoint, accessToken);
};

const downloadReport = async (accessToken, reportId) => {
  const documentInfo = await getReportDocument(accessToken, reportId);
  const response = await fetch(documentInfo.payload.url);
  const content = await response.text();
  if (documentInfo.payload.compressionAlgorithm === 'GZIP') {
    const decompressed = await decompressGzip(content);
    return decompressed;
  }
  return content;
};
Enter fullscreen mode Exit fullscreen mode

Benachrichtigungs-API

Webhook-Abonnement erstellen

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);
};
Enter fullscreen mode Exit fullscreen mode

SNS-Ziel einrichten

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-Richtlinie
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'
    }
  ]
};
Enter fullscreen mode Exit fullscreen mode

Benachrichtigungen verarbeiten

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;
  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());
  switch (message.Type) {
    case 'SubscriptionConfirmation':
      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;
  }
}
Enter fullscreen mode Exit fullscreen mode

Ratenbegrenzungen und Quoten

Limits pro Endpunkt

Endpunkt-Kategorie Ratenbegrenzung Burst-Grenze
Bestellungen 10 Anfragen/Sekunde 20
Bestellpositionen 5 Anfragen/Sekunde 10
Inventar 2 Anfragen/Sekunde 5
Angebote 10 Anfragen/Sekunde 20
Berichte 0,5 Anfragen/Sekunde 1
Benachrichtigungen 1 Anfrage/Sekunde 2
FBA Inbound 2 Anfragen/Sekunde 5

Rate-Limit aus dem Header x-amzn-RateLimit-Limit auslesen.

Exponentielles Backoff implementieren

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);
      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) {
        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) {
        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;
      }
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Anfragewarteschlange

class RateLimitedQueue {
  constructor(rateLimit, burstLimit = null) {
    this.rateLimit = rateLimit;
    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;
  }
}

// Beispiel: Orders API Queue (10 req/s)
const ordersQueue = new RateLimitedQueue(10, 20);
const orders = await ordersQueue.add(() => getOrders(accessToken, options));
Enter fullscreen mode Exit fullscreen mode

Sicherheits-Best-Practices

Credentials sicher speichern

Nie im Code! Immer Umgebungsvariablen oder Secrets Manager nutzen:

// Falsch:
const AWS_ACCESS_KEY = 'AKIAIOSFODNN7EXAMPLE';
// Richtig:
const AWS_ACCESS_KEY = process.env.AWS_ACCESS_KEY_ID;
// Besser:
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);
};
Enter fullscreen mode Exit fullscreen mode

Token-Verschlüsselung

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

IAM Least Privilege

{
  "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/*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

SigV4-Request-Timestamp prüfen

const validateTimestamp = (amzDate) => {
  const now = new Date();
  const requestTime = new Date(amzDate);
  const diff = Math.abs(now - requestTime);
  if (diff > 5 * 60 * 1000) {
    throw new Error('Request timestamp too old. Sync your server clock.');
  }
};
Enter fullscreen mode Exit fullscreen mode

SP-API-Integrationen mit Apidog testen

SP-API OpenAPI-Spezifikation importieren

  1. Spezifikation von Amazon GitHub herunterladen
  2. Neues Projekt in Apidog erstellen
  3. Spezifikationsdatei importieren
  4. Umgebungsvariablen für Credentials konfigurieren

Pre-Request-Skripte für SigV4

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;

const signer = new SigV4Signer(accessKey, secretKey, region);
const signedHeaders = signer.sign(method, url.href, body, {
  'x-amz-access-token': accessToken
});
apidog.request.headers = {
  ...apidog.request.headers,
  ...signedHeaders.headers
};
Enter fullscreen mode Exit fullscreen mode

Testszenarien für Workflows

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');
const orderIds = ordersResponse.data.payload.orders.map(o => o.amazon_order_id);
apidog.variables.set('order_ids', JSON.stringify(orderIds));
Enter fullscreen mode Exit fullscreen mode

Mock-Responses für Entwicklung

// 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
  }
}
Enter fullscreen mode Exit fullscreen mode

CI/CD-Integration

# 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"
Enter fullscreen mode Exit fullscreen mode

Referenz der Marktplatz-IDs

Land Marktplatz-ID
Vereinigte Staaten ATVPDKIKX0DER
Kanada A2EUQ1WTGCTBG2
Mexiko A1AM78C64UM0Y8
Vereinigtes Königreich A1F83G8C2ARO7P
Deutschland A1PA6795UKMFR9
Frankreich A13V1IB3VIYZZH
Italien APJ6JRA9NG5V4
Spanien A1RKKUPIHCS9HS
Japan A1VC38T7YXB528
Australien A39IBJ37TRP1C6
Indien A21TJRUUN4KGV
Brasilien A2Q3Y263D00KWC

Fehlerbehebung bei häufigen Problemen

403 Unautorisiert

  • AWS-Credentials und IAM-Rolle prüfen
  • OAuth-Token-Gültigkeit
  • SigV4-Signatur (Region, Zeitstempel)
  • Header x-amz-access-token gesetzt

429 Rate Limit überschritten

  • Anforderungswarteschlange & Backoff verwenden
  • Anfragen bündeln (Paginierung)
  • Header x-amzn-RateLimit-Limit überwachen
  • Höhere Limits ggf. bei Amazon beantragen

404 Nicht gefunden

  • Richtigen API-Endpunkt & Marktplatz-ID nutzen
  • Ressource existiert?
  • API-Version im Pfad prüfen

400 Ungültige Anfrage

  • ISO8601-Datumsformat verwenden
  • Erforderliche Felder prüfen
  • Richtige IDs
  • JSON-Syntax kontrollieren
const validateIsoDate = (dateString) => {
  const date = new Date(dateString);
  if (isNaN(date.getTime())) {
    throw new Error('Invalid ISO 8601 date format');
  }
  return dateString;
};
Enter fullscreen mode Exit fullscreen mode

Berichte bleiben im INIT_STATE

  • Geduld (15-30 Minuten)
  • Berichtstyp für Konto verfügbar?
  • Datumsbereich verkleinern
  • Polling alle 30 Sekunden

Benachrichtigungen kommen nicht an

  • Abonnementstatus prüfen
  • SNS-Topic-Richtlinie korrekt?
  • Endpoint öffentlich & SSL
  • 200 OK binnen 30 Sekunden antworten

Produktions-Checkliste

  • [ ] App in Produktions-Seller Central registrieren
  • [ ] Produktive IAM-Rolle mit minimalen Berechtigungen
  • [ ] OAuth-Redirect-URIs prüfen
  • [ ] Token-Speicherung verschlüsselt
  • [ ] Token-Refresh automatisieren
  • [ ] Ratenbegrenzung & Queue implementieren
  • [ ] SNS-Ziel für Webhooks
  • [ ] Fehlerbehandlung vollständig
  • [ ] Logging mit Request-IDs
  • [ ] Monitoring für API-Limits
  • [ ] Runbook für Supportfälle
  • [ ] Tests mit mehreren Marktplatz-IDs
  • [ ] Seller-Onboarding dokumentieren
  • [ ] Retry-Logik mit Backoff
  • [ ] Auth-Fehler überwachen

Monitoring/Alarmierung

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 }
};
const failureRate = metrics.apiCalls.failed / metrics.apiCalls.total;
if (failureRate > 0.05) { sendAlert('SP-API failure rate above 5%'); }
for (const [endpoint, usage] of Object.entries(metrics.rateLimitUsage)) {
  if (usage.current / usage.limit > 0.8) {
    sendAlert(`${endpoint} rate limit at 80% capacity`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Praxisbeispiele

Multi-Marktplatz-Bestandssynchronisation

  • Problem: Überverkäufe durch manuelle Updates
  • Lösung: Zentrales System + SP-API-Webhooks + Queue
  • Ablauf: SNS → Sync → API-Queue → Audit

Automatisierte Auftragsabwicklung

  • Problem: 200+ Bestellungen/Tag, manuell
  • Lösung: SP-API an WMS angebunden
  • Integration: Webhook → WMS → Tracking → SP-API → Kunde

Analyse-Dashboard

  • Problem: Kein Multi-Marktplatz-Reporting
  • Lösung: OAuth-Flow für jeden Verkäufer, Token-Verwaltung
  • Daten: Bestellungen, Lager, Angebote, Markenreports

Fazit

Die Amazon SP-API bietet umfassenden Zugriff auf Seller Central-Funktionen – mit OAuth 2.0, IAM-Rollen und SigV4-Signierung. Beachte:

  • Credentials & Token-Refresh automatisieren
  • SigV4 immer korrekt signieren
  • Ratenbegrenzungen pro Endpunkt aktiv managen
  • SNS-Benachrichtigungen für Echtzeitdaten nutzen
  • Robuste Fehlerbehandlung & Retry-Logik
  • Apidog für API-Tests & Teamwork einsetzen

FAQ-Bereich

Was ist die Amazon SP-API?

Die Amazon Selling Partner API (SP-API) ist eine REST-basierte API, die programmatischen Zugriff auf Seller Central-Daten wie Bestellungen, Lagerbestand, Angebote und Berichte bietet. Sie ersetzte das ältere MWS-System durch verbesserte Sicherheit durch OAuth 2.0 und AWS SigV4-Signierung.

Wie erhalte ich Amazon SP-API-Anmeldeinformationen?

Registriere deine Anwendung in Seller Central unter Apps und Dienste > Apps entwickeln. Du erhältst eine Client-ID, ein Client-Geheimnis und eine Anwendungs-ID. Zusätzlich muss eine IAM-Rolle in AWS erstellt und mit der Anwendung verknüpft werden.

Ist die Amazon SP-API kostenlos nutzbar?

Ja, der SP-API-Zugang ist für registrierte Amazon-Verkäufer kostenlos. Es gelten jedoch Ratenbegrenzungen je Endpunkt (0,5 bis 100 Anfragen/Sekunde). Für hohe Volumina kann Amazon höhere Limits gewähren.

Welche Authentifizierung verwendet die SP-API?

OAuth 2.0 zur Autorisierung plus AWS IAM-Rollen zur Zugriffssteuerung. Alle API-Anfragen benötigen eine AWS SigV4-Signatur mit temporären Credentials aus dem OAuth-Flow.

Wie gehe ich mit SP-API-Ratenbegrenzungen um?

Implementiere eine Anforderungswarteschlange und Backoff-Strategien. Überwache den x-amzn-RateLimit-Limit-Header und reagiere auf 429-Fehler mit Retry/Delay.

Kann ich die SP-API ohne Verkäuferkonto testen?

Ja. Amazon bietet eine Sandbox-Umgebung für die SP-API-Entwicklung. Registriere deine App im Sandbox-Modus. Nicht alle Endpunkte sind dort verfügbar.

Wie funktionieren Webhooks mit der SP-API?

Die SP-API nutzt Amazon SNS für Benachrichtigungen. Erstelle ein Abonnement, konfiguriere ein SNS-Topic und implementiere einen HTTPS-Endpunkt. Bestätige Abonnementnachrichten automatisch.

Was passiert, wenn das OAuth-Token abläuft?

LWA-Zugriffstoken laufen nach 1 Stunde ab. Mit dem Refresh-Token kannst du neue Tokens erhalten. Automatisiere den Refresh-Prozess.

Wie migriere ich von MWS zur SP-API?

MWS wird bis Dezember 2025 eingestellt. Migration: OAuth 2.0 statt MWS-Token, SigV4-Signierung, neue Endpunkt-URLs, von XML zu JSON wechseln.

Warum erhalte ich 403 Unautorisierte Fehler?

Typische Ursachen: Abgelaufenes OAuth-Token, fehlerhafte IAM-Rolle, falsche SigV4-Signatur, fehlender x-amz-access-token-Header oder falsche Rollenzuweisung. Prüfe den Fehlercode in der API-Antwort.

Top comments (0)