Cómo Integrar la API de Amazon SP: Tutorial Paso a Paso

Ashley Innocent

Ashley Innocent

20 March 2026

Cómo Integrar la API de Amazon SP: Tutorial Paso a Paso

Resumen

La API de Amazon Selling Partner (SP-API) es una API basada en REST que permite el acceso programático a los datos del vendedor para pedidos, inventario, listados y cumplimiento. Utiliza autenticación OAuth 2.0 con roles de IAM, requiere la firma AWS SigV4 y aplica límites de tasa que varían según el punto final (0.1 a 100 solicitudes por segundo). Esta guía cubre la configuración de la cuenta, la autenticación, los puntos finales principales, las suscripciones a webhooks y las estrategias de implementación en producción.

Introducción

Amazon procesa más de 350 millones de productos en más de 200 mercados en todo el mundo. Para los desarrolladores que crean herramientas de comercio electrónico, sistemas de gestión de inventario o plataformas de análisis, la integración de la API de Amazon SP no es opcional. Es esencial.

Aquí está la realidad: los vendedores que gestionan operaciones en Amazon pierden de 20 a 30 horas semanales en la entrada manual de datos en pedidos, inventario y listados. Una integración sólida de la API de SP automatiza la sincronización de pedidos, las actualizaciones de inventario y la gestión de listados en múltiples mercados.

Esta guía lo lleva a través de todo el proceso de integración de la API de Amazon SP. Aprenderá la configuración del rol de IAM, la autorización OAuth 2.0, la firma de solicitudes AWS SigV4, la gestión de pedidos e inventario, las suscripciones a notificaciones y la resolución de problemas de errores. Al final, tendrá una integración de Amazon lista para producción.

💡
Apidog simplifica las pruebas de integración de API. Pruebe sus puntos finales de SP-API, valide los flujos de OAuth, inspeccione las firmas de solicitud y depure los problemas de autenticación en un solo espacio de trabajo. Importe especificaciones de API, simule respuestas y comparta escenarios de prueba con su equipo.

botón

¿Qué es la API de Amazon SP?

La API de Amazon Selling Partner (SP-API) es una API basada en REST que proporciona acceso programático a los datos centrales del vendedor. Reemplazó al antiguo Marketplace Web Service (MWS) con seguridad, rendimiento y funcionalidad mejorados.

Capacidades Clave

La API de SP gestiona:

Comparación SP-API vs MWS

Característica SP-API MWS (Legado)
Arquitectura JSON RESTful Basado en XML
Autenticación OAuth 2.0 + IAM Token de autenticación MWS
Seguridad Firma AWS SigV4 Tokens simples
Límites de tasa Dinámicos por punto final Cuotas fijas
Mercados Puntos finales unificados Específicos de la región
Estado Actual Obsoleto (Dic 2025)

Migre cualquier integración de MWS a SP-API de inmediato. Amazon anunció la retirada completa de MWS para diciembre de 2025.

Resumen de la arquitectura de la API

Amazon utiliza una estructura de API regional con autorización centralizada:

https://sellingpartnerapi-na.amazon.com (North America)
https://sellingpartnerapi-eu.amazon.com (Europe)
https://sellingpartnerapi-fe.amazon.com (Far East)

Todas las solicitudes requieren:

  1. Firma AWS SigV4
  2. Token de acceso del flujo de OAuth
  3. Permisos de rol de IAM adecuados
  4. ID de solicitud para el rastreo

Mercados soportados

Región Mercados Punto final de la API
América del Norte EE. UU., CA, MX sellingpartnerapi-na.amazon.com
Europa Reino Unido, DE, FR, IT, ES, NL, SE, PL, TR, EG, IN, AE, SA sellingpartnerapi-eu.amazon.com
Lejano Oriente JP, AU, SG, BR sellingpartnerapi-fe.amazon.com

Primeros pasos: Configuración de cuenta e IAM

Paso 1: Cree su cuenta de desarrollador de Amazon

Antes de acceder a la API de SP, necesita un acceso de cuenta adecuado:

  1. Visite el Centro de Desarrolladores de Amazon
  2. Inicie sesión con su cuenta de Amazon (debe tener acceso a Seller Central)
  3. Navegue a Selling Partner API en el panel de control
  4. Acepte el Acuerdo de Desarrollador

Paso 2: Registre su aplicación

Cree un perfil de aplicación en Seller Central:

  1. Inicie sesión en Seller Central
  2. Navegue a Aplicaciones y Servicios > Desarrollar Aplicaciones
  3. Haga clic en Añadir Nueva Aplicación
  4. Rellene los detalles de la aplicación:

Después de la presentación, recibirá:

Nota de seguridad: Almacene las credenciales en variables de entorno, nunca en el código:

# .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"

Paso 3: Cree un rol de IAM para la API de SP

La API de SP requiere un rol de IAM con permisos específicos:

  1. Inicie sesión en la Consola de AWS IAM
  2. Navegue a Roles > Crear Rol
  3. Seleccione Otra cuenta de AWS como entidad de confianza
  4. Ingrese el ID de cuenta de Amazon para su región:

Paso 4: Configure la política de IAM

Adjunte esta política a su rol de IAM:

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

Asigne a su rol un nombre descriptivo como SellingPartnerApiRole y anote el ARN.

Paso 5: Vincule el rol de IAM a la aplicación

Conecte su rol de IAM a la aplicación SP-API:

  1. Vuelva a Seller Central > Desarrollar aplicaciones
  2. Seleccione su aplicación
  3. Haga clic en Editar > ARN del rol de IAM
  4. Ingrese el ARN de su rol de IAM
  5. Guarde los cambios

Amazon valida el rol de IAM en cuestión de minutos. Verá un estado de “Vinculado” cuando esté listo.

Flujo de autenticación OAuth 2.0

Entendiendo OAuth de la API de SP

Amazon utiliza OAuth 2.0 para la autorización. Aquí está el flujo completo:

1. El vendedor hace clic en "Autorizar" en su aplicación
2. Su aplicación redirige a la URL de autorización de Amazon
3. El vendedor inicia sesión y concede permisos
4. Amazon redirige de vuelta con el código de autorización
5. Su aplicación intercambia el código por el token LWA (Login con Amazon)
6. Su aplicación intercambia el token LWA por el token de acceso de SP-API
7. Su aplicación utiliza el token de acceso para las llamadas a la API (firmadas con SigV4)
8. Actualice el token cuando expire el token de acceso (1 hora)

Paso 6: Genere la URL de autorización

Cree la URL de autorización de 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, // Cadena aleatoria para protección CSRF
    scope: 'sellingpartnerapi::notifications'
  });

  return `${baseUrl}?${params.toString()}`;
};

// Uso
const authUrl = generateAuthUrl(
  process.env.AMAZON_CLIENT_ID,
  'https://your-app.com/callback',
  crypto.randomBytes(16).toString('hex')
);

console.log(`Redirect user to: ${authUrl}`);

Ámbitos de OAuth requeridos

Solicite solo los permisos que necesita su aplicación:

Ámbito Descripción Caso de uso
sellingpartnerapi::notifications Recibir notificaciones Suscripciones a Webhooks
sellingpartnerapi::migration Migrar desde MWS Integraciones heredadas

La mayoría del acceso a la API está controlado por políticas de IAM, no por ámbitos de OAuth.

Paso 7: Intercambie el código por un token LWA

Maneje la devolución de llamada de OAuth e intercambie el código de autorización:

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, // Típicamente 3600 segundos (1 hora)
    token_type: data.token_type
  };
};

// Manejar la ruta de devolución de llamada
app.get('/callback', async (req, res) => {
  const { spapi_oauth_code, state } = req.query;

  // Verificar que el estado coincida con lo que envió (protección 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');

    // Almacenar tokens en la base de datos asociados con el vendedor
    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');
  }
});

Paso 8: Intercambie el token LWA por credenciales de SP-API

Utilice el token de acceso LWA para obtener credenciales temporales de AWS:

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

  // Intercambio de credenciales de AWS a través de 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;
};

Paso 9: Implemente la actualización de tokens

Los tokens de acceso expiran después de 1 hora. Implemente la actualización automática:

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, // Siempre guarde el nuevo token de actualización
    expires_in: data.expires_in
  };
};

// Middleware para asegurar un token válido
const ensureValidToken = async (sellerId) => {
  const seller = await db.sellers.findById(sellerId);

  // Comprobar si el token expira dentro de 5 minutos
  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;
};

Firma de solicitudes AWS SigV4

Comprendiendo SigV4

Todas las solicitudes a la API de SP requieren la firma AWS Signature Version 4 (SigV4). Esto garantiza la autenticidad e integridad de la solicitud.

Proceso de firma 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();

    // Paso 1: Crear solicitud canónica
    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');

    // Paso 2: Crear cadena para firmar
    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');

    // Paso 3: Calcular firma
    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');

    // Paso 4: Añadir encabezado de autorización
    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);
  }
}

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

Uso del SDK de AWS para SigV4

Simplifique la firma con el SDK de 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 de Pedidos

Recuperación de pedidos

Obtenga pedidos con opciones de filtrado:

const getOrders = async (accessToken, options = {}) => {
  const params = new URLSearchParams({
    createdAfter: options.createdAfter, // Formato ISO 8601
    createdBefore: options.createdBefore,
    orderStatuses: options.orderStatuses?.join(',') || '',
    marketplaceIds: options.marketplaceIds?.join(',') || ['ATVPDKIKX0DER'], // EE. UU.
    maxResultsPerPage: options.maxResultsPerPage || 100
  });

  // Eliminar parámetros vacíos
  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);
};

// Ejemplo de uso
const orders = await getOrders(accessToken, {
  createdAfter: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), // Últimas 24 horas
  orderStatuses: ['Unshipped', 'PartiallyShipped'],
  marketplaceIds: ['ATVPDKIKX0DER'] // Mercado de EE. UU.
});

Estructura de respuesta de pedido

{
  "payload": {
    "orders": [
      {
        "amazon_order_id": "112-1234567-1234567",
        "seller_order_id": "ORDER-001",
        "purchase_date": "2026-03-19T10:30:00Z",
        "last_update_date": "2026-03-19T14:45:00Z",
        "order_status": "Unshipped",
        "fulfillment_channel": "AFN", // AFN = FBA, MFN = Vendedor
        "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=="
  }
}

Obtención de artículos del pedido

Recupere los artículos de línea detallados de un pedido:

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

  return makeSpApiRequest('GET', endpoint, accessToken);
};

// Uso
const orderItems = await getOrderItems(accessToken, '112-1234567-1234567');

// Respuesta esperada
{
  "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."
        }
      }
    ]
  }
}

Actualización del estado del envío

Marque los pedidos como enviados con información de seguimiento:

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, // por ejemplo, '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);
};

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

Códigos comunes de transportistas

Transportista Código de transportista
USPS USPS
FedEx FEDEX
UPS UPS
DHL DHL
Correos de Canadá CANADA_POST
Royal Mail ROYAL_MAIL
Correos de Australia AUSTRALIA_POST
Logística de Amazon AMZN_UK

API de Inventario

Obtención de resúmenes de inventario

Obtenga niveles de inventario en todos los mercados:

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

// Uso
const inventory = await getInventorySummaries(accessToken, {
  granularityId: 'ATVPDKIKX0DER',
  sellerSkus: ['MYSKU-001', 'MYSKU-002']
});

Estructura de respuesta de inventario

{
  "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"
      }
    ]
  }
}

Actualización de inventario

Nota: La API de SP no proporciona puntos finales directos para la actualización de inventario. El inventario se gestiona a través de:

  1. Envíos FBA - Enviar inventario a los almacenes de Amazon
  2. Pedidos MFN - El inventario disminuye automáticamente cuando se envían los pedidos
  3. Actualizaciones de listados - Ajustar la cantidad a través de la API de Listados

Para FBA, cree planes de envío:

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 de Listados

Obtención de listados

Obtenga listados de productos con filtrado:

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

// Uso
const listings = await getListings(accessToken, {
  identifiers: ['B08N5WRWNW', 'B09JQKJXYZ'],
  itemTypes: ['ASIN']
});

Estructura de respuesta de listado

{
  "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
}

Creación o actualización de listados

Utilice submitListingsSubmission para operaciones por lotes:

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

Eliminación de un listado

Elimine o desactive un listado:

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 de Informes

Creación de programaciones de informes

Automatice la generación de informes:

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

// Tipos de informes comunes
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'
};

// Uso
const report = await createReport(accessToken, REPORT_TYPES.ORDERS, {
  marketplaceIds: ['ATVPDKIKX0DER'],
  startTime: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // Últimos 7 días
  endTime: new Date()
});

Obtención del documento de informe

Descargue el informe generado:

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

  return makeSpApiRequest('GET', endpoint, accessToken);
};

// Descargar y analizar informe
const downloadReport = async (accessToken, reportId) => {
  const documentInfo = await getReportDocument(accessToken, reportId);

  const response = await fetch(documentInfo.payload.url);
  const content = await response.text();

  // Los informes suelen estar delimitados por tabulaciones o en formato JSON
  if (documentInfo.payload.compressionAlgorithm === 'GZIP') {
    const decompressed = await decompressGzip(content);
    return decompressed;
  }

  return content;
};

API de Notificaciones

Creación de suscripciones

Configure webhooks para eventos en tiempo real:

const createSubscription = async (accessToken, subscriptionData) => {
  const endpoint = 'https://sellingpartnerapi-na.amazon.com/notifications/v1/subscriptions';

  const payload = {
    payload: {
      destination: {
        resource: subscriptionData.destinationArn, // ARN del tema de SNS
        name: subscriptionData.name
      },
      modelVersion: '1.0',
      eventFilter: {
        eventCode: subscriptionData.eventCode,
        marketplaceIds: subscriptionData.marketplaceIds
      }
    }
  };

  return makeSpApiRequest('POST', endpoint, accessToken, payload);
};

// Tipos de eventos 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'
};

// Uso
await createSubscription(accessToken, {
  destinationArn: 'arn:aws:sns:us-east-1:123456789012:sp-api-notifications',
  name: 'OrderStatusNotifications',
  eventCode: EVENT_CODES.ORDER_STATUS_CHANGE,
  marketplaceIds: ['ATVPDKIKX0DER']
});

Configuración del destino de SNS

Amazon envía notificaciones a los temas de 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 política del tema de SNS debe permitir que Amazon SES publique
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'
    }
  ]
};

Procesamiento de notificaciones

Configure un punto final de SNS para recibir notificaciones:

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;

  // Verificar la firma del mensaje de 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());

  // Manejar diferentes tipos de mensajes
  switch (message.Type) {
    case 'SubscriptionConfirmation':
      // Confirmación automática de la suscripción
      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;
  }
}

Límites de tasa y cuotas

Entendiendo los límites de tasa

La API de SP impone límites de tasa dinámicos por punto final:

Categoría de punto final Límite de tasa Límite de ráfaga
Pedidos 10 solicitudes/segundo 20
Artículos del Pedido 5 solicitudes/segundo 10
Inventario 2 solicitudes/segundo 5
Listados 10 solicitudes/segundo 20
Informes 0.5 solicitudes/segundo 1
Notificaciones 1 solicitud/segundo 2
Entrada FBA 2 solicitudes/segundo 5

Compruebe el encabezado x-amzn-RateLimit-Limit en las respuestas para conocer los límites actuales.

Implementación del manejo de límites de tasa

Utilice retroceso exponencial para reintentos:

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

      // Comprobar encabezados de límite de tasa
      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) {
        // Extraer encabezado retry-after o usar retroceso exponencial
        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) {
        // Servicio no disponible - retroceso exponencial
        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;
      }
    }
  }
};

Implementación de cola de solicitudes

Implemente una cola para mantenerse dentro de los límites:

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

// Uso - Cola de la API de pedidos (10 solicitudes/s)
const ordersQueue = new RateLimitedQueue(10, 20);
const orders = await ordersQueue.add(() => getOrders(accessToken, options));

Mejores prácticas de seguridad

Gestión de credenciales

Nunca codifique las credenciales directamente en su código fuente. Utilice variables de entorno o un gestor de secretos:

// Malo - nunca haga esto
const AWS_ACCESS_KEY = 'AKIAIOSFODNN7EXAMPLE';
const AWS_SECRET = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY';

// Bueno - use variables de entorno
const AWS_ACCESS_KEY = process.env.AWS_ACCESS_KEY_ID;
const AWS_SECRET = process.env.AWS_SECRET_ACCESS_KEY;

// Mejor - use AWS Secrets Manager o similar
const { SecretsManagerClient } = require('@aws-sdk/client-secrets-manager');
const secretsClient = new SecretsManagerClient({ region: 'us-east-1' });

const getCredentials = async () => {
  const response = await secretsClient.send({
    Name: 'prod/sp-api/credentials'
  });
  return JSON.parse(response.SecretString);
};

Requisitos de almacenamiento de tokens

La API de SP requiere medidas de seguridad específicas para el almacenamiento de tokens:

  1. Cifrar en reposo: Use cifrado AES-256 para los tokens almacenados
  2. Cifrar en tránsito: Siempre use HTTPS/TLS 1.2+
  3. Controles de acceso: Limite el acceso a los tokens a cuentas de servicio específicas
  4. Registro de auditoría: Registre todos los accesos a tokens y eventos de actualización
  5. Rotación automática: Actualice los tokens antes de su vencimiento
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;
  }
}

Privilegio mínimo de IAM

Conceda solo los permisos que necesita su aplicación:

{
  "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/*"
    }
  ]
}

Evite usar comodines * en producción. Limite los permisos a puntos finales específicos.

Seguridad de la firma de solicitudes

Siempre valide su implementación de SigV4:

  1. Use HTTPS para todas las solicitudes
  2. Incluya todos los encabezados requeridos en la firma
  3. Asegúrese de que las marcas de tiempo estén dentro de los 5 minutos de los servidores de AWS
  4. Rote las credenciales de AWS regularmente
  5. Use roles de IAM en lugar de credenciales a largo plazo cuando sea posible
// Validar marca de tiempo de la solicitud
const validateTimestamp = (amzDate) => {
  const now = new Date();
  const requestTime = new Date(amzDate);
  const diff = Math.abs(now - requestTime);

  // AWS rechaza solicitudes con más de 5 minutos de antigüedad
  if (diff > 5 * 60 * 1000) {
    throw new Error('Request timestamp too old. Sync your server clock.');
  }
};

Prueba de integraciones de SP-API con Apidog

Por qué probar las integraciones de SP-API

Las integraciones de la API de Amazon SP implican flujos de autenticación complejos, múltiples puntos finales y estrictos límites de tasa. Las pruebas le ayudan a:

Configuración de Apidog para pruebas de SP-API

Paso 1: Importar la especificación OpenAPI de SP-API

Apidog soporta especificaciones OpenAPI 3.0. Importe la especificación SP-API de Amazon:

  1. Descargue la especificación OpenAPI de SP-API del repositorio de Amazon
  2. En Apidog, cree un nuevo proyecto
  3. Importe el archivo de especificación
  4. Configure las variables de entorno para las credenciales

Paso 2: Configure las variables de entorno

Configure variables específicas del entorno:

URL base (Sandbox): https://sandbox.sellingpartnerapi-na.amazon.com
URL base (Producción): https://sellingpartnerapi-na.amazon.com
Token de acceso LWA: {{lwa_access_token}}
Clave de acceso de AWS: {{aws_access_key}}
Clave secreta de AWS: {{aws_secret_key}}
Región: us-east-1

Paso 3: Cree scripts de pre-solicitud

Automatice la firma SigV4 con los scripts de pre-solicitud de Apidog:

// Script de pre-solicitud de Apidog para firma 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;

// Generar firma SigV4
const signer = new SigV4Signer(accessKey, secretKey, region);
const signedHeaders = signer.sign(method, url.href, body, {
  'x-amz-access-token': accessToken
});

// Establecer encabezados para la solicitud
apidog.request.headers = {
  ...apidog.request.headers,
  ...signedHeaders.headers
};

Paso 4: Cree escenarios de prueba

Construya escenarios de prueba para flujos de trabajo comunes:

// Prueba: Obtener pedidos de las últimas 24 horas
// 1. Intercambiar código OAuth por token
// 2. Llamar al punto final GetOrders
// 3. Validar estructura de respuesta
// 4. Extraer IDs de pedido
// 5. Llamar a GetOrderItems para cada pedido

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

// Almacenar IDs de pedido para solicitudes posteriores
const orderIds = ordersResponse.data.payload.orders.map(o => o.amazon_order_id);
apidog.variables.set('order_ids', JSON.stringify(orderIds));

Simulación de respuestas de la API de SP

Utilice la función de simulación inteligente de Apidog para simular respuestas de la API de SP durante el desarrollo:

// Respuesta simulada para 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
  }
}

Esto permite que el desarrollo frontend continúe sin acceder a los puntos finales reales de Amazon.

Depuración de problemas comunes

Problema: Desajuste de la firma SigV4

Utilice el inspector de solicitudes de Apidog para verificar:

  1. Todos los encabezados requeridos están incluidos
  2. Los encabezados están ordenados alfabéticamente
  3. La solicitud canónica coincide con las expectativas de AWS
  4. La marca de tiempo está dentro del rango válido

Problema: Errores de token OAuth

Verifique la validez del token con una solicitud simple:

const tokenCheck = await apidog.send({
  method: 'GET',
  url: '/orders/v0/orders',
  params: { createdAfter: new Date().toISOString() }
});

if (tokenCheck.status === 401) {
  console.log('Token expired - refresh required');
  // Activar flujo de actualización de token
}

Automatización de pruebas en CI/CD

Integre las pruebas de Apidog en su pipeline de CI/CD:

# Ejemplo de GitHub Actions
name: SP-API Integration Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Ejecutar pruebas de Apidog
        uses: apidog/test-action@v1
        with:
          project-id: ${{ secrets.APIDOG_PROJECT_ID }}
          api-key: ${{ secrets.APIDOG_API_KEY }}
          environment: sandbox

      - name: Notificar en caso de fallo
        if: failure()
        run: |
          echo "Las pruebas de SP-API fallaron - revise el panel de Apidog"

Referencia de ID de Mercado

Mantenga esta referencia a mano para los mercados comunes:

País ID de Mercado
Estados Unidos ATVPDKIKX0DER
Canadá A2EUQ1WTGCTBG2
México A1AM78C64UM0Y8

Practica el diseño de API en Apidog

Descubre una forma más fácil de construir y usar APIs