Intégration API Amazon SP : Tutoriel Pas à Pas

Ashley Innocent

Ashley Innocent

20 March 2026

Intégration API Amazon SP : Tutoriel Pas à Pas

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.

💡
Apidog simplifie les tests d'intégration d'API. Testez vos points de terminaison SP-API, validez les flux OAuth, inspectez les signatures de requêtes et déboguez les problèmes d'authentification dans un seul espace de travail. Importez des spécifications d'API, simulez des réponses et partagez des scénarios de test avec votre équipe.
bouton

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 :

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 :

  1. Signature AWS SigV4
  2. Jeton d'accès du flux OAuth
  3. Autorisations de rôle IAM appropriées
  4. 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é :

  1. Visitez le Centre Développeur Amazon
  2. Connectez-vous avec votre compte Amazon (doit avoir accès à Seller Central)
  3. Accédez à l'API Selling Partner dans le tableau de bord
  4. Acceptez l'Accord Développeur

Étape 2 : Enregistrer Votre Application

Créez un profil d'application dans Seller Central :

  1. Connectez-vous à Seller Central
  2. Accédez à Applications et Services > Développer des applications
  3. Cliquez sur Ajouter une nouvelle application
  4. Remplissez les détails de l'application :

Après soumission, vous recevrez :

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 :

  1. Connectez-vous à la Console AWS IAM
  2. Accédez à Rôles > Créer un rôle
  3. Sélectionnez Un autre compte AWS comme entité de confiance
  4. Saisissez l'ID de compte d'Amazon pour votre région :

É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 :

  1. Retournez à Seller Central > Développer des applications
  2. Sélectionnez votre application
  3. Cliquez sur Modifier > ARN du rôle IAM
  4. Saisissez l'ARN de votre rôle IAM
  5. 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 :

  1. Expéditions FBA - Envoyer l'inventaire aux entrepôts Amazon
  2. Commandes MFN - L'inventaire diminue automatiquement lorsque les commandes sont expédiées
  3. 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 :

  1. Chiffrement au repos : Utilisez le chiffrement AES-256 pour les jetons stockés
  2. Chiffrement en transit : Toujours utiliser HTTPS/TLS 1.2+
  3. Contrôles d'accès : Limiter l'accès aux jetons aux comptes de service spécifiques
  4. Journalisation d'audit : Enregistrer tous les accès aux jetons et événements d'actualisation
  5. 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 :

  1. Utilisez HTTPS pour toutes les requêtes
  2. Incluez tous les en-têtes requis dans la signature
  3. Assurez-vous que les horodatages sont à moins de 5 minutes des serveurs AWS
  4. Faites pivoter régulièrement les identifiants AWS
  5. 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.');
  }
};

Tester les Int

Pratiquez le Design-first d'API dans Apidog

Découvrez une manière plus simple de créer et utiliser des API