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.
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:
- Recuperación de pedidos y actualizaciones de estado
- Gestión de inventario en todos los mercados
- Creación, actualización y eliminación de listados
- Gestión de envíos FBA (Logística de Amazon)
- Precios de productos y análisis competitivo
- Generación de informes y análisis
- Gestión de Contenido A+
- Análisis de marca y datos publicitarios
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:
- Firma AWS SigV4
- Token de acceso del flujo de OAuth
- Permisos de rol de IAM adecuados
- 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:
- Visite el Centro de Desarrolladores de Amazon
- Inicie sesión con su cuenta de Amazon (debe tener acceso a Seller Central)
- Navegue a Selling Partner API en el panel de control
- Acepte el Acuerdo de Desarrollador
Paso 2: Registre su aplicación
Cree un perfil de aplicación en Seller Central:
- Inicie sesión en Seller Central
- Navegue a Aplicaciones y Servicios > Desarrollar Aplicaciones
- Haga clic en Añadir Nueva Aplicación
- Rellene los detalles de la aplicación:
- Nombre de la aplicación: Nombre claro y descriptivo
- Tipo de aplicación: Seleccione “Autodesarrollada” o “Terceros”
- Caso de uso: Describa el propósito de su integración
- URI de redirección: URL HTTPS para la devolución de llamada de OAuth
Después de la presentación, recibirá:
- ID de la aplicación: Su identificador de aplicación pública
- ID de cliente: Se utiliza en la URL de autorización de OAuth
- Secreto de cliente: Su secreto de API privado (nunca lo exponga)
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:
- Inicie sesión en la Consola de AWS IAM
- Navegue a Roles > Crear Rol
- Seleccione Otra cuenta de AWS como entidad de confianza
- Ingrese el ID de cuenta de Amazon para su región:
- América del Norte:
906394416454 - Europa:
336853085554 - Lejano Oriente:
774466381866
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:
- Vuelva a Seller Central > Desarrollar aplicaciones
- Seleccione su aplicación
- Haga clic en Editar > ARN del rol de IAM
- Ingrese el ARN de su rol de IAM
- 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:
- Envíos FBA - Enviar inventario a los almacenes de Amazon
- Pedidos MFN - El inventario disminuye automáticamente cuando se envían los pedidos
- 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:
- Cifrar en reposo: Use cifrado AES-256 para los tokens almacenados
- Cifrar en tránsito: Siempre use HTTPS/TLS 1.2+
- Controles de acceso: Limite el acceso a los tokens a cuentas de servicio específicas
- Registro de auditoría: Registre todos los accesos a tokens y eventos de actualización
- 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:
- Use HTTPS para todas las solicitudes
- Incluya todos los encabezados requeridos en la firma
- Asegúrese de que las marcas de tiempo estén dentro de los 5 minutos de los servidores de AWS
- Rote las credenciales de AWS regularmente
- 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:
- Validar flujos de autorización OAuth 2.0
- Depurar problemas de firma SigV4
- Probar el manejo de errores para límites de tasa
- Simular respuestas para el desarrollo
- Documentar flujos de trabajo de API para su equipo
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:
- Descargue la especificación OpenAPI de SP-API del repositorio de Amazon
- En Apidog, cree un nuevo proyecto
- Importe el archivo de especificación
- 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:
- Todos los encabezados requeridos están incluidos
- Los encabezados están ordenados alfabéticamente
- La solicitud canónica coincide con las expectativas de AWS
- 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 |
