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.
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)
Jede Anfrage benötigt:
- AWS SigV4-Signatur
- Zugangstoken aus dem OAuth-Flow
- Richtige IAM-Rollenberechtigungen
- 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
- Gehe zu Amazon Developer Central
- Mit Amazon-Konto anmelden (muss Seller Central Zugriff haben)
- Im Dashboard: Selling Partner API auswählen
- Entwicklervereinbarung akzeptieren
Schritt 2: Anwendung in Seller Central registrieren
- In Seller Central einloggen
- Apps und Dienste > Apps entwickeln
- Neue App hinzufügen
- 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"
Schritt 3: IAM-Rolle für SP-API erstellen
- In der AWS IAM-Konsole anmelden
- Rollen > Rolle erstellen
- Anderes AWS-Konto als vertrauenswürdige Entität wählen
- Konto-ID für Region angeben:
- Nordamerika:
906394416454 - Europa:
336853085554 - Fernost:
774466381866
- Nordamerika:
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/*"
]
}
]
}
Rolle sinnvoll benennen (z.B. SellingPartnerApiRole) und ARN notieren.
Schritt 5: IAM-Rolle mit Anwendung verknüpfen
- In Seller Central: Apps entwickeln auswählen
- Anwendung auswählen, Bearbeiten > IAM Role ARN
- Rollen-ARN eintragen, Änderungen speichern
- Nach wenigen Minuten Status „Verknüpft“
OAuth 2.0 Authentifizierungsflow
Ablauf
- Verkäufer klickt in der App auf „Autorisieren“
- Redirect zur Amazon-Autorisierungs-URL
- Verkäufer gewährt Berechtigungen
- Amazon gibt Autorisierungscode zurück
- App tauscht Code gegen LWA-Token (Login with Amazon)
- LWA-Token gegen SP-API-Zugriffstoken eintauschen
- Zugriffstoken für signierte API-Calls nutzen
- 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}`);
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');
}
});
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;
};
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;
};
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
});
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();
};
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']
});
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');
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 }
]
});
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']
});
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);
};
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']
});
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);
};
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);
};
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);
};
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;
};
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);
};
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'
}
]
};
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;
}
}
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;
}
}
}
};
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));
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);
};
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;
}
}
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/*"
}
]
}
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.');
}
};
SP-API-Integrationen mit Apidog testen
SP-API OpenAPI-Spezifikation importieren
- Spezifikation von Amazon GitHub herunterladen
- Neues Projekt in Apidog erstellen
- Spezifikationsdatei importieren
- 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
};
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));
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
}
}
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"
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-tokengesetzt
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;
};
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`);
}
}
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)