En bref
L'API Amazon Selling Partner (SP-API) est une API REST qui permet un accès programmatique aux données du vendeur pour les commandes, l'inventaire, les fiches produits et l'exécution des commandes. Elle utilise l'authentification OAuth 2.0 avec des rôles IAM, nécessite la signature AWS SigV4 et applique des limites de débit qui varient selon les points de terminaison (de 0,1 à 100 requêtes par seconde). Ce guide couvre la configuration du compte, l'authentification, les points de terminaison principaux, les abonnements aux webhooks et les stratégies de déploiement en production.
Introduction
Amazon gère plus de 350 millions de produits sur plus de 200 places de marché dans le monde. Pour les développeurs qui créent des outils de commerce électronique, des systèmes de gestion des stocks ou des plateformes d'analyse, l'intégration de l'API Amazon SP n'est pas une option. C'est essentiel.
Voici la réalité : les vendeurs gérant des opérations Amazon perdent 20 à 30 heures par semaine en saisie manuelle de données pour les commandes, l'inventaire et les fiches produits. Une solide intégration SP-API automatise la synchronisation des commandes, les mises à jour de l'inventaire et la gestion des fiches produits sur plusieurs places de marché.
Ce guide vous présente le processus complet d'intégration de l'API Amazon SP. Vous apprendrez la configuration des rôles IAM, l'autorisation OAuth 2.0, la signature des requêtes AWS SigV4, la gestion des commandes et des stocks, les abonnements aux notifications et le dépannage des erreurs. À la fin, vous disposerez d'une intégration Amazon prête pour la production.
Qu'est-ce que l'API Amazon SP ?
L'API Amazon Selling Partner (SP-API) est une API REST qui fournit un accès programmatique aux données centrales du vendeur. Elle a remplacé l'ancien Marketplace Web Service (MWS) par une sécurité, des performances et des fonctionnalités améliorées.
Fonctionnalités Clés
SP-API gère :
- Récupération des commandes et mises à jour de statut
- Gestion des stocks sur les places de marché
- Création, mise à jour et suppression de fiches produits
- Gestion des expéditions FBA (Expédié par Amazon)
- Tarification des produits et analyse concurrentielle
- Génération de rapports et d'analyses
- Gestion du contenu A+
- Analyses de marque et données publicitaires
Comparaison SP-API vs MWS
| Caractéristique | SP-API | MWS (Hérité) |
|---|---|---|
| Architecture | RESTful JSON | Basée sur XML |
| Authentification | OAuth 2.0 + IAM | Jeton d'authentification MWS |
| Sécurité | Signature AWS SigV4 | Jetons simples |
| Limites de débit | Dynamique par point de terminaison | Quotas fixes |
| Places de marché | Points de terminaison unifiés | Spécifique à la région |
| Statut | Actuel | Obsolète (Déc 2025) |
Migrez immédiatement toutes les intégrations MWS vers SP-API. Amazon a annoncé le retrait complet de MWS pour décembre 2025.
Vue d'ensemble de l'architecture API
Amazon utilise une structure API régionale avec une autorisation centralisée :
https://sellingpartnerapi-na.amazon.com (Amérique du Nord)
https://sellingpartnerapi-eu.amazon.com (Europe)
https://sellingpartnerapi-fe.amazon.com (Extrême-Orient)
Toutes les requêtes nécessitent :
- Signature AWS SigV4
- Jeton d'accès du flux OAuth
- Autorisations de rôle IAM appropriées
- ID de requête pour le traçage
Places de marché prises en charge
| Région | Places de marché | Point de terminaison API |
|---|---|---|
| Amérique du Nord | 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 |
| Extrême-Orient | JP, AU, SG, BR | sellingpartnerapi-fe.amazon.com |
Démarrage : Configuration du Compte et d'IAM
Étape 1 : Créer Votre Compte Développeur Amazon
Avant d'accéder à SP-API, vous avez besoin d'un accès au compte approprié :
- Visitez le Centre Développeur Amazon
- Connectez-vous avec votre compte Amazon (doit avoir accès à Seller Central)
- Accédez à l'API Selling Partner dans le tableau de bord
- Acceptez l'Accord Développeur
Étape 2 : Enregistrer Votre Application
Créez un profil d'application dans Seller Central :
- Connectez-vous à Seller Central
- Accédez à Applications et Services > Développer des applications
- Cliquez sur Ajouter une nouvelle application
- Remplissez les détails de l'application :
- Nom de l'application : Nom clair et descriptif
- Type d'application : Sélectionnez « Auto-développée » ou « Tierce partie »
- Cas d'utilisation : Décrivez l'objectif de votre intégration
- URI de redirection : URL HTTPS pour le rappel OAuth
Après soumission, vous recevrez :
- ID d'application : Votre identifiant d'application public
- ID client : Utilisé dans l'URL d'autorisation OAuth
- Secret client : Votre secret API privé (ne jamais le divulguer)
Note de sécurité : Stockez les identifiants dans des variables d'environnement, jamais dans le code :
# Fichier .env
AMAZON_APPLICATION_ID="amzn1.application.xxxxx"
AMAZON_CLIENT_ID="amzn1.account.xxxxx"
AMAZON_CLIENT_SECRET="votre_secret_client_ici"
AMAZON_SELLER_ID="votre_id_vendeur_ici"
AWS_ACCESS_KEY_ID="votre_cle_d_acces_aws"
AWS_SECRET_ACCESS_KEY="votre_cle_secrete_aws"
AWS_REGION="us-east-1"
Étape 3 : Créer un Rôle IAM pour SP-API
SP-API nécessite un rôle IAM avec des autorisations spécifiques :
- Connectez-vous à la Console AWS IAM
- Accédez à Rôles > Créer un rôle
- Sélectionnez Un autre compte AWS comme entité de confiance
- Saisissez l'ID de compte d'Amazon pour votre région :
- Amérique du Nord :
906394416454 - Europe :
336853085554 - Extrême-Orient :
774466381866
Étape 4 : Configurer la Politique IAM
Attachez cette politique à votre rôle IAM :
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"execute-api:Invoke"
],
"Resource": [
"arn:aws:execute-api:*:*:*/prod/*/sellingpartnerapi/*"
]
}
]
}
Nommez votre rôle de manière descriptive, par exemple SellingPartnerApiRole et notez l'ARN.
Étape 5 : Lier le Rôle IAM à l'Application
Connectez votre rôle IAM à l'application SP-API :
- Retournez à Seller Central > Développer des applications
- Sélectionnez votre application
- Cliquez sur Modifier > ARN du rôle IAM
- Saisissez l'ARN de votre rôle IAM
- Enregistrez les modifications
Amazon valide le rôle IAM en quelques minutes. Vous verrez un statut « Lié » lorsque ce sera prêt.
Flux d'Authentification OAuth 2.0
Comprendre l'OAuth SP-API
Amazon utilise OAuth 2.0 pour l'autorisation. Voici le flux complet :
1. Le vendeur clique sur "Autoriser" dans votre application
2. Votre application redirige vers l'URL d'autorisation Amazon
3. Le vendeur se connecte et accorde les autorisations
4. Amazon redirige l'utilisateur avec un code d'autorisation
5. Votre application échange le code contre un jeton LWA (Login with Amazon)
6. Votre application échange le jeton LWA contre un jeton d'accès SP-API
7. Votre application utilise le jeton d'accès pour les appels API (signés SigV4)
8. Actualisez le jeton lorsque le jeton d'accès expire (1 heure)
Étape 6 : Générer l'URL d'Autorisation
Créez l'URL d'autorisation OAuth :
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()}`;
};
// Utilisation
const authUrl = generateAuthUrl(
process.env.AMAZON_CLIENT_ID,
'https://your-app.com/callback',
crypto.randomBytes(16).toString('hex')
);
console.log(`Rediriger l'utilisateur vers : ${authUrl}`);
Portées OAuth Requises
Demandez uniquement les autorisations dont votre application a besoin :
| Portée | Description | Cas d'utilisation |
|---|---|---|
sellingpartnerapi::notifications |
Recevoir des notifications | Abonnements aux webhooks |
sellingpartnerapi::migration |
Migrer depuis MWS | Intégrations héritées |
La plupart des accès API sont contrôlés par les politiques IAM, et non par les portées OAuth.
Étape 7 : Échanger le Code contre un Jeton LWA
Gérez le rappel OAuth et échangez le code d'autorisation :
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
};
};
// Gérer la route de rappel
app.get('/callback', async (req, res) => {
const { spapi_oauth_code, state } = req.query;
// Vérifier que l'état correspond à ce que vous avez envoyé (protection CSRF)
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');
// Stocker les jetons dans la base de données associée au vendeur
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('Échange de jeton échoué :', error);
res.status(500).send('Authentification échouée');
}
});
Étape 8 : Échanger le Jeton LWA contre des Identifiants SP-API
Utilisez le jeton d'accès LWA pour obtenir des identifiants AWS temporaires :
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();
// Échanger contre des identifiants AWS 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;
};
Étape 9 : Implémenter l'Actualisation du Jeton
Les jetons d'accès expirent après 1 heure. Implémentez l'actualisation automatique :
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, // Toujours enregistrer le nouveau jeton d'actualisation
expires_in: data.expires_in
};
};
// Middleware pour assurer un jeton valide
const ensureValidToken = async (sellerId) => {
const seller = await db.sellers.findById(sellerId);
// Vérifier si le jeton expire dans les 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;
};
Signature des requêtes AWS SigV4
Comprendre SigV4
Toutes les requêtes SP-API nécessitent la signature AWS Signature Version 4 (SigV4). Cela garantit l'authenticité et l'intégrité des requêtes.
Processus de signature SigV4
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();
// Étape 1 : Créer la requête canonique
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');
// Étape 2 : Créer la chaîne à signer
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');
// Étape 3 : Calculer la 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');
// Étape 4 : Ajouter l'en-tête d'autorisation
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);
}
}
// Utilisation
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
});
Utiliser le SDK AWS pour SigV4
Simplifiez la signature avec le SDK AWS :
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();
};
API Commandes
Récupérer des Commandes
Récupérer les commandes avec des options de filtrage :
const getOrders = async (accessToken, options = {}) => {
const params = new URLSearchParams({
createdAfter: options.createdAfter, // Format ISO 8601
createdBefore: options.createdBefore,
orderStatuses: options.orderStatuses?.join(',') || '',
marketplaceIds: options.marketplaceIds?.join(',') || ['ATVPDKIKX0DER'], // US
maxResultsPerPage: options.maxResultsPerPage || 100
});
// Supprimer les paramètres vides
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);
};
// Exemple d'utilisation
const orders = await getOrders(accessToken, {
createdAfter: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), // Dernières 24 heures
orderStatuses: ['Unshipped', 'PartiallyShipped'],
marketplaceIds: ['ATVPDKIKX0DER'] // Place de marché américaine
});
Structure de la Réponse de Commande
{
"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 = Expédié par Amazon, MFN = Expédié par le vendeur
"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=="
}
}
Obtenir les Articles de Commande
Récupérer les articles détaillés d'une commande :
const getOrderItems = async (accessToken, orderId) => {
const endpoint = `https://sellingpartnerapi-na.amazon.com/orders/v0/orders/${orderId}/orderItems`;
return makeSpApiRequest('GET', endpoint, accessToken);
};
// Utilisation
const orderItems = await getOrderItems(accessToken, '112-1234567-1234567');
// Réponse attendue
{
"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."
}
}
]
}
}
Mettre à Jour le Statut d'Expédition
Marquer les commandes comme expédiées avec les informations de suivi :
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);
};
// Utilisation
await confirmShipment(accessToken, '112-1234567-1234567', {
carrierCode: 'USPS',
trackingNumber: '9400111899223456789012',
items: [
{ orderItemId: '12345678901234', quantity: 2 }
]
});
Codes de Transporteur Courants
| Transporteur | Code Transporteur |
|---|---|
| USPS | USPS |
| FedEx | FEDEX |
| UPS | UPS |
| DHL | DHL |
| Postes Canada | CANADA_POST |
| Royal Mail | ROYAL_MAIL |
| Poste australienne | AUSTRALIA_POST |
| Amazon Logistics | AMZN_UK |
API Inventaire
Obtenir les Résumés d'Inventaire
Récupérer les niveaux de stock sur les places de marché :
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);
};
// Utilisation
const inventory = await getInventorySummaries(accessToken, {
granularityId: 'ATVPDKIKX0DER',
sellerSkus: ['MYSKU-001', 'MYSKU-002']
});
Structure de la Réponse d'Inventaire
{
"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"
}
]
}
}
Mettre à Jour l'Inventaire
Note : SP-API ne fournit pas de points de terminaison directs pour la mise à jour des stocks. L'inventaire est géré via :
- Expéditions FBA - Envoyer l'inventaire aux entrepôts Amazon
- Commandes MFN - L'inventaire diminue automatiquement lorsque les commandes sont expédiées
- Mises à jour de fiches produits - Ajuster la quantité via l'API Fiches produits
Pour FBA, créez des plans d'expédition :
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);
};
API Fiches Produits
Obtenir les Fiches Produits
Récupérer les fiches produits avec filtrage :
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);
};
// Utilisation
const listings = await getListings(accessToken, {
identifiers: ['B08N5WRWNW', 'B09JQKJXYZ'],
itemTypes: ['ASIN']
});
Structure de la Réponse de Fiche Produit
{
"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
}
Créer ou Mettre à Jour des Fiches Produits
Utilisez `submitListingsSubmission` pour les opérations par lots :
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);
};
Supprimer une Fiche Produit
Supprimer ou désactiver une fiche produit :
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);
};
API Rapports
Créer des Planifications de Rapports
Automatisez la génération de rapports :
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);
};
// Types de rapports courants
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'
};
// Utilisation
const report = await createReport(accessToken, REPORT_TYPES.ORDERS, {
marketplaceIds: ['ATVPDKIKX0DER'],
startTime: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 derniers jours
endTime: new Date()
});
Obtenir le Document de Rapport
Téléchargez le rapport généré :
const getReportDocument = async (accessToken, reportId) => {
const endpoint = `https://sellingpartnerapi-na.amazon.com/reports/2021-06-30/reports/${reportId}/document`;
return makeSpApiRequest('GET', endpoint, accessToken);
};
// Télécharger et analyser le rapport
const downloadReport = async (accessToken, reportId) => {
const documentInfo = await getReportDocument(accessToken, reportId);
const response = await fetch(documentInfo.payload.url);
const content = await response.text();
// Les rapports sont généralement délimités par des tabulations ou au format JSON
if (documentInfo.payload.compressionAlgorithm === 'GZIP') {
const decompressed = await decompressGzip(content);
return decompressed;
}
return content;
};
API Notifications
Créer des Abonnements
Configurez des webhooks pour les événements en temps réel :
const createSubscription = async (accessToken, subscriptionData) => {
const endpoint = 'https://sellingpartnerapi-na.amazon.com/notifications/v1/subscriptions';
const payload = {
payload: {
destination: {
resource: subscriptionData.destinationArn, // ARN du topic SNS
name: subscriptionData.name
},
modelVersion: '1.0',
eventFilter: {
eventCode: subscriptionData.eventCode,
marketplaceIds: subscriptionData.marketplaceIds
}
}
};
return makeSpApiRequest('POST', endpoint, accessToken, payload);
};
// Types d'événements disponibles
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'
};
// Utilisation
await createSubscription(accessToken, {
destinationArn: 'arn:aws:sns:us-east-1:123456789012:sp-api-notifications',
name: 'OrderStatusNotifications',
eventCode: EVENT_CODES.ORDER_STATUS_CHANGE,
marketplaceIds: ['ATVPDKIKX0DER']
});
Configurer la Destination SNS
Amazon envoie des notifications aux topics SNS :
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 });
};
// La politique du topic SNS doit autoriser Amazon SES à publier
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'
}
]
};
Traiter les Notifications
Configurez un point de terminaison SNS pour recevoir les 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;
// Vérifier la signature du message SNS
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());
// Gérer les différents types de messages
switch (message.Type) {
case 'SubscriptionConfirmation':
// Confirmer automatiquement l'abonnement
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;
}
}
Limitation du Débit et Quotas
Comprendre les Limites de Débit
SP-API applique des limites de débit dynamiques par point de terminaison :
| Catégorie de Point de Terminaison | Limite de Débit | Limite de Rafale |
|---|---|---|
| Commandes | 10 requêtes/seconde | 20 |
| Articles de Commande | 5 requêtes/seconde | 10 |
| Inventaire | 2 requêtes/seconde | 5 |
| Fiches Produits | 10 requêtes/seconde | 20 |
| Rapports | 0.5 requêtes/seconde | 1 |
| Notifications | 1 requête/seconde | 2 |
| FBA Entrant | 2 requêtes/seconde | 5 |
Vérifiez l'en-tête x-amzn-RateLimit-Limit dans les réponses pour connaître les limites actuelles.
Implémenter la Gestion de la Limitation du Débit
Utilisez un backoff exponentiel pour les réessais :
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);
// Vérifier les en-têtes de limitation de débit
const rateLimit = response.headers.get('x-amzn-RateLimit-Limit');
const retryAfter = response.headers.get('Retry-After');
if (retryAfter) {
console.warn(`Limite de débit atteinte. Réessayez après : ${retryAfter} secondes`);
}
return response;
} catch (error) {
if (error.message.includes('429') && attempt < maxRetries) {
// Extraire l'en-tête retry-after ou utiliser un backoff exponentiel
const retryAfter = error.headers?.get('Retry-After') || Math.pow(2, attempt);
console.log(`Limite de débit atteinte. Réessai dans ${retryAfter}s...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
} else if (error.message.includes('503') && attempt < maxRetries) {
// Service indisponible - backoff exponentiel
const delay = Math.pow(2, attempt) * 1000;
console.log(`Service indisponible. Réessai dans ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
};
Implémentation de la File d'Attente de Requêtes
Implémentez une file d'attente pour rester dans les limites :
class RateLimitedQueue {
constructor(rateLimit, burstLimit = null) {
this.rateLimit = rateLimit; // requêtes par seconde
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;
}
}
// Utilisation - File d'attente API Commandes (10 req/s)
const ordersQueue = new RateLimitedQueue(10, 20);
const orders = await ordersQueue.add(() => getOrders(accessToken, options));
Bonnes Pratiques de Sécurité
Gestion des Identifiants
Ne jamais coder en dur les identifiants dans votre code source. Utilisez des variables d'environnement ou un gestionnaire de secrets :
// Mauvaise pratique - ne jamais faire cela
const AWS_ACCESS_KEY = 'AKIAIOSFODNN7EXAMPLE';
const AWS_SECRET = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY';
// Bonne pratique - utiliser des variables d'environnement
const AWS_ACCESS_KEY = process.env.AWS_ACCESS_KEY_ID;
const AWS_SECRET = process.env.AWS_SECRET_ACCESS_KEY;
// Meilleure pratique - utiliser AWS Secrets Manager ou similaire
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);
};
Exigences de Stockage des Jetons
SP-API exige des mesures de sécurité spécifiques pour le stockage des jetons :
- Chiffrement au repos : Utilisez le chiffrement AES-256 pour les jetons stockés
- Chiffrement en transit : Toujours utiliser HTTPS/TLS 1.2+
- Contrôles d'accès : Limiter l'accès aux jetons aux comptes de service spécifiques
- Journalisation d'audit : Enregistrer tous les accès aux jetons et événements d'actualisation
- Rotation automatique : Actualiser les jetons avant 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 Moindre Privilège
N'accordez que les autorisations dont votre application a besoin :
{
"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/*"
}
]
}
Évitez d'utiliser les caractères génériques * en production. Limitez les autorisations à des points de terminaison spécifiques.
Sécurité de la Signature de Requête
Validez toujours votre implémentation SigV4 :
- Utilisez HTTPS pour toutes les requêtes
- Incluez tous les en-têtes requis dans la signature
- Assurez-vous que les horodatages sont à moins de 5 minutes des serveurs AWS
- Faites pivoter régulièrement les identifiants AWS
- Utilisez des rôles IAM au lieu d'identifiants à long terme lorsque cela est possible
// Valider l'horodatage de la requête
const validateTimestamp = (amzDate) => {
const now = new Date();
const requestTime = new Date(amzDate);
const diff = Math.abs(now - requestTime);
// AWS rejette les requêtes de plus de 5 minutes
if (diff > 5 * 60 * 1000) {
throw new Error('Horodatage de la requête trop ancien. Synchronisez l\'horloge de votre serveur.');
}
};
