DEV Community

Cover image for Como Integrar a API SP da Amazon: Tutorial Passo a Passo
Lucas
Lucas

Posted on • Originally published at apidog.com

Como Integrar a API SP da Amazon: Tutorial Passo a Passo

RESUMO

A Amazon Selling Partner API (SP-API) é uma API REST que permite acesso programático a dados de vendedores para pedidos, estoque, listagens e fulfillment. Ela utiliza autenticação OAuth 2.0 com funções IAM, exige a assinatura AWS SigV4 e impõe limites de taxa que variam por endpoint (0,1 a 100 requisições por segundo). Este guia abrange a configuração da conta, autenticação, endpoints principais, assinaturas de webhook e estratégias de implantação em produção.

Experimente o Apidog hoje

💡 Dica: Apidog simplifica o teste de integração de API. Teste seus endpoints SP-API, valide fluxos OAuth, inspecione assinaturas de requisição e depure problemas de autenticação em um único ambiente de trabalho. Importe especificações de API, simule respostas e compartilhe cenários de teste com sua equipe.

O Que É a Amazon SP-API?

A Amazon Selling Partner API (SP-API) é uma API REST robusta para acessar dados do Seller Central. Ela substitui o antigo MWS, trazendo melhorias em segurança, desempenho e recursos.

Principais Funcionalidades

Com a SP-API você automatiza:

  • Recuperação e atualização de pedidos
  • Gerenciamento de estoque em múltiplos marketplaces
  • Criação, atualização e exclusão de listagens
  • Gestão de remessas FBA
  • Precificação e análise competitiva
  • Geração de relatórios e análises
  • Gerenciamento de Conteúdo A+
  • Análise de marca e dados de publicidade

Comparativo SP-API vs MWS

Funcionalidade SP-API MWS (Legado)
Arquitetura JSON RESTful Baseado em XML
Autenticação OAuth 2.0 + IAM Token MWS
Segurança AWS SigV4 Tokens simples
Limites de Taxa Dinâmico por endpoint Cotas fixas
Marketplaces Endpoints unificados Específicos região
Status Atual Obsoleto (Dez 2025)

Migre suas integrações MWS para a SP-API o quanto antes. O MWS será desativado em dezembro de 2025.

Visão Geral da Arquitetura da API

A Amazon oferece endpoints regionais:

https://sellingpartnerapi-na.amazon.com (América do Norte)
https://sellingpartnerapi-eu.amazon.com (Europa)
https://sellingpartnerapi-fe.amazon.com (Extremo Oriente)
Enter fullscreen mode Exit fullscreen mode

Todas as requisições exigem:

  1. Assinatura AWS SigV4
  2. Token de acesso OAuth
  3. Permissões de IAM corretas
  4. ID de requisição para rastreamento

Marketplaces Suportados

Região Marketplaces Endpoint da API
América do Norte EUA, CA, MX sellingpartnerapi-na.amazon.com
Europa Reino Unido, Alemanha, França, Itália, Espanha, Holanda, Suécia, Polônia, Turquia, Egito, Índia, Emirados Árabes Unidos, Arábia Saudita sellingpartnerapi-eu.amazon.com
Extremo Oriente Japão, Austrália, Cingapura, Brasil sellingpartnerapi-fe.amazon.com

Primeiros Passos: Configuração da Conta e IAM

Passo 1: Crie Sua Conta de Desenvolvedor Amazon

  1. Acesse o Amazon Developer Central
  2. Faça login com uma conta com acesso ao Seller Central
  3. Navegue até Selling Partner API
  4. Aceite o Contrato de Desenvolvedor

Passo 2: Registre Seu Aplicativo

  1. No Seller Central
  2. Navegue até Aplicativos e Serviços > Desenvolver Aplicativos
  3. Clique em Adicionar Novo Aplicativo
  4. Preencha com:
    • Nome do Aplicativo
    • Tipo de Aplicativo (“Desenvolvido por você” ou “Terceiro”)
    • Caso de Uso (descrição)
    • URI de Redirecionamento (HTTPS, para OAuth)

Após o registro, você recebe:

  • ID do Aplicativo
  • ID do Cliente
  • Segredo do Cliente

Armazene essas credenciais em variáveis de ambiente. Nunca exponha no código:

# .env
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"
Enter fullscreen mode Exit fullscreen mode

Passo 3: Crie uma Função IAM para a SP-API

  1. No Console AWS IAM
  2. Em Funções > Criar Função
  3. Selecione Outra conta AWS
  4. Insira o ID da conta Amazon da sua região:
    • América do Norte: 906394416454
    • Europa: 336853085554
    • Extremo Oriente: 774466381866

Passo 4: Configure a Política IAM

Anexe esta política à função:

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

Dê um nome descritivo, como SellingPartnerApiRole, e anote o ARN.

Passo 5: Vincule a Função IAM ao Aplicativo

  1. No Seller Central > Desenvolver Aplicativos
  2. Selecione o aplicativo
  3. Clique em Editar > ARN da Função IAM
  4. Insira o ARN da função
  5. Salve. Após alguns minutos, deve aparecer como “Vinculado”.

Fluxo de Autenticação OAuth 2.0

Entendendo o OAuth da SP-API

Fluxo resumido:

1. Usuário autoriza seu app (OAuth)
2. Redirecionamento para Amazon
3. Usuário concede permissões
4. Amazon retorna com código de autorização
5. Seu app troca por token LWA
6. Seu app troca token LWA por token SP-API
7. Use token SP-API nas requisições (assinadas com SigV4)
8. Renove o token quando expirar (1h)
Enter fullscreen mode Exit fullscreen mode

Passo 6: Gerar URL de Autorização

const generateAuthUrl = (clientId, redirectUri, state) => {
  const baseUrl = 'https://www.amazon.com/sp/apps/oauth/authorize';
  const params = new URLSearchParams({
    application_id: process.env.AMAZON_APPLICATION_ID,
    client_id: clientId,
    redirect_uri: redirectUri,
    state: state,
    scope: 'sellingpartnerapi::notifications'
  });
  return `${baseUrl}?${params.toString()}`;
};

// Exemplo de uso:
const authUrl = generateAuthUrl(
  process.env.AMAZON_CLIENT_ID,
  'https://your-app.com/callback',
  crypto.randomBytes(16).toString('hex')
);
console.log(`Redirecionar usuário para: ${authUrl}`);
Enter fullscreen mode Exit fullscreen mode

Escopos OAuth Necessários

Escopo Descrição Caso de Uso
sellingpartnerapi::notifications Receber notificações Webhooks
sellingpartnerapi::migration Migrar do MWS Integrações legadas

A maioria dos acessos é controlada por IAM, não pelos escopos OAuth.

Passo 7: Trocar Código por Token LWA

const exchangeCodeForLwaToken = async (code, redirectUri) => {
  const response = await fetch('https://api.amazon.com/auth/o2/token', {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: process.env.AMAZON_CLIENT_ID,
      client_secret: process.env.AMAZON_CLIENT_SECRET,
      redirect_uri: redirectUri,
      code: code
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`LWA Token Error: ${error.error_description}`);
  }

  const data = await response.json();

  return {
    access_token: data.access_token,
    refresh_token: data.refresh_token,
    expires_in: data.expires_in,
    token_type: data.token_type
  };
};

// Rota de callback OAuth
app.get('/callback', async (req, res) => {
  const { spapi_oauth_code, state } = req.query;
  if (state !== req.session.oauthState) {
    return res.status(400).send('Invalid state parameter');
  }
  try {
    const tokens = await exchangeCodeForLwaToken(spapi_oauth_code, 'https://your-app.com/callback');
    await db.sellers.update(req.session.sellerId, {
      amazon_lwa_access_token: tokens.access_token,
      amazon_lwa_refresh_token: tokens.refresh_token,
      amazon_token_expires: Date.now() + (tokens.expires_in * 1000)
    });
    res.redirect('/dashboard');
  } catch (error) {
    console.error('Token exchange failed:', error);
    res.status(500).send('Authentication failed');
  }
});
Enter fullscreen mode Exit fullscreen mode

Passo 8: Trocar Token LWA por Credenciais SP-API

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

  // Troca para credenciais 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;
};
Enter fullscreen mode Exit fullscreen mode

Passo 9: Implementar Renovação de Token

const refreshLwaToken = async (refreshToken) => {
  const response = await fetch('https://api.amazon.com/auth/o2/token', {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      client_id: process.env.AMAZON_CLIENT_ID,
      client_secret: process.env.AMAZON_CLIENT_SECRET,
      refresh_token: refreshToken
    })
  });

  const data = await response.json();

  return {
    access_token: data.access_token,
    refresh_token: data.refresh_token,
    expires_in: data.expires_in
  };
};

// Middleware para garantir token válido
const ensureValidToken = async (sellerId) => {
  const seller = await db.sellers.findById(sellerId);
  if (seller.amazon_token_expires < Date.now() + 300000) {
    const newTokens = await refreshLwaToken(seller.amazon_lwa_refresh_token);
    await db.sellers.update(sellerId, {
      amazon_lwa_access_token: newTokens.access_token,
      amazon_lwa_refresh_token: newTokens.refresh_token,
      amazon_token_expires: Date.now() + (newTokens.expires_in * 1000)
    });
    return newTokens.access_token;
  }
  return seller.amazon_lwa_access_token;
};
Enter fullscreen mode Exit fullscreen mode

Assinatura de Requisição AWS SigV4

Entendendo o SigV4

Toda requisição SP-API precisa ser assinada com AWS SigV4 para garantir autenticidade e integridade.

Processo de Assinatura 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();
    const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, '');
    const dateStamp = amzDate.slice(0, 8);
    headers['host'] = parsedUrl.host;
    headers['x-amz-date'] = amzDate;
    headers['x-amz-access-token'] = this.accessToken;
    headers['content-type'] = 'application/json';

    const canonicalHeaders = Object.entries(headers)
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([k, v]) => `${k.toLowerCase()}:${v.trim()}`)
      .join('\n');

    const signedHeaders = Object.keys(headers)
      .sort()
      .map(k => k.toLowerCase())
      .join(';');

    const payloadHash = crypto.createHash('sha256').update(body).digest('hex');

    const canonicalRequest = [
      method.toUpperCase(),
      parsedUrl.pathname,
      parsedUrl.search.slice(1),
      canonicalHeaders,
      '',
      signedHeaders,
      payloadHash
    ].join('\n');

    const algorithm = 'AWS4-HMAC-SHA256';
    const credentialScope = `${dateStamp}/${this.region}/${this.service}/aws4_request`;

    const stringToSign = [
      algorithm,
      amzDate,
      credentialScope,
      crypto.createHash('sha256').update(canonicalRequest).digest('hex')
    ].join('\n');

    const kDate = this.hmac(`AWS4${this.secretKey}`, dateStamp);
    const kRegion = this.hmac(kDate, this.region);
    const kService = this.hmac(kRegion, this.service);
    const kSigning = this.hmac(kService, 'aws4_request');
    const signature = this.hmac(kSigning, stringToSign, 'hex');

    const authorization = `${algorithm} Credential=${this.accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;

    return {
      headers: {
        ...headers,
        'Authorization': authorization
      },
      canonicalRequest,
      stringToSign,
      signature
    };
  }
  hmac(key, data, encoding = 'buffer') {
    return crypto.createHmac('sha256', key).update(data).digest(encoding);
  }
}

// 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
});
Enter fullscreen mode Exit fullscreen mode

Usando o SDK da AWS para SigV4

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();
};
Enter fullscreen mode Exit fullscreen mode

API de Pedidos

Recuperando Pedidos

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

  for (const [key, value] of params.entries()) {
    if (!value) params.delete(key);
  }

  const endpoint = `https://sellingpartnerapi-na.amazon.com/orders/v0/orders?${params.toString()}`;

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

// Exemplo:
const orders = await getOrders(accessToken, {
  createdAfter: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
  orderStatuses: ['Unshipped', 'PartiallyShipped'],
  marketplaceIds: ['ATVPDKIKX0DER']
});
Enter fullscreen mode Exit fullscreen mode

Estrutura da Resposta 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",
        "sales_channel": "Amazon.com",
        "order_total": {
          "currency_code": "USD",
          "amount": "89.99"
        },
        "number_of_items_shipped": 0,
        "number_of_items_unshipped": 2,
        "marketplace_id": "ATVPDKIKX0DER",
        "is_prime": true
      }
    ],
    "next_token": "eyJleHBpcmF0aW9uVGltZU9mTmV4dFRva2VuIjoxNzEwOTUwNDAwfQ=="
  }
}
Enter fullscreen mode Exit fullscreen mode

Obtendo Itens do 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');
Enter fullscreen mode Exit fullscreen mode

Atualizando o Status da Remessa

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

  const payload = {
    packageDetails: {
      packageReferenceId: shipmentData.packageReferenceId || '1',
      carrier_code: shipmentData.carrierCode,
      tracking_number: shipmentData.trackingNumber,
      ship_date: shipmentData.shipDate || new Date().toISOString(),
      items: shipmentData.items.map(item => ({
        order_item_id: item.orderItemId,
        quantity: item.quantity
      }))
    }
  };

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

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

Códigos de Transportadora Comuns

Transportadora Código
USPS USPS
FedEx FEDEX
UPS UPS
DHL DHL
Correios Canadá CANADA_POST
Royal Mail ROYAL_MAIL
Correios Austrália AUSTRALIA_POST
Logística Amazon AMZN_UK

API de Estoque

Obtendo Resumos de Estoque

const getInventorySummaries = async (accessToken, options = {}) => {
  const params = new URLSearchParams({
    granularityType: options.granularityType || 'Marketplace',
    granularityId: options.granularityId || 'ATVPDKIKX0DER',
    startDateTime: options.startDateTime || '',
    sellerSkus: options.sellerSkus?.join(',') || ''
  });

  const endpoint = `https://sellingpartnerapi-na.amazon.com/fba/inventory/v1/summaries?${params.toString()}`;
  return makeSpApiRequest('GET', endpoint, accessToken);
};

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

Atualizando o Estoque

A SP-API não permite ajuste direto de estoque. Faça via:

  1. Remessas FBA – envie para depósitos Amazon
  2. Pedidos MFN – estoque diminui automaticamente quando enviado
  3. Atualização de Listagem – ajuste quantidade via API de Listagens

Exemplo FBA:

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);
};
Enter fullscreen mode Exit fullscreen mode

API de Listagens

Obtendo Listagens

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

// Exemplo
const listings = await getListings(accessToken, {
  identifiers: ['B08N5WRWNW', 'B09JQKJXYZ'],
  itemTypes: ['ASIN']
});
Enter fullscreen mode Exit fullscreen mode

Criando ou Atualizando Listagens

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);
};
Enter fullscreen mode Exit fullscreen mode

Excluindo uma Listagem

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);
};
Enter fullscreen mode Exit fullscreen mode

API de Relatórios

Criando Agendamentos de Relatórios

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

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

// Exemplo
const report = await createReport(accessToken, REPORT_TYPES.ORDERS, {
  marketplaceIds: ['ATVPDKIKX0DER'],
  startTime: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
  endTime: new Date()
});
Enter fullscreen mode Exit fullscreen mode

Obtendo Documento de Relatório

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

// Download do relatório
const downloadReport = async (accessToken, reportId) => {
  const documentInfo = await getReportDocument(accessToken, reportId);
  const response = await fetch(documentInfo.payload.url);
  const content = await response.text();

  if (documentInfo.payload.compressionAlgorithm === 'GZIP') {
    const decompressed = await decompressGzip(content);
    return decompressed;
  }
  return content;
};
Enter fullscreen mode Exit fullscreen mode

API de Notificações

Criando Assinaturas

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

  const payload = {
    payload: {
      destination: {
        resource: subscriptionData.destinationArn,
        name: subscriptionData.name
      },
      modelVersion: '1.0',
      eventFilter: {
        eventCode: subscriptionData.eventCode,
        marketplaceIds: subscriptionData.marketplaceIds
      }
    }
  };

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

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']
});
Enter fullscreen mode Exit fullscreen mode

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

// Política do tópico SNS:
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'
    }
  ]
};
Enter fullscreen mode Exit fullscreen mode

Processando Notificações

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 assinatura da mensagem 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());

  switch (message.Type) {
    case 'SubscriptionConfirmation':
      await fetch(message.SubscribeURL);
      break;
    case 'Notification':
      const notification = JSON.parse(message.Message);
      await handleSpApiNotification(notification);
      break;
  }

  res.status(200).send('OK');
});

async function handleSpApiNotification(notification) {
  const { notificationType, payload } = notification;
  switch (notificationType) {
    case 'OrderStatusChange':
      await syncOrderStatus(payload.amazonOrderId);
      break;
    case 'OrderChange':
      await syncOrderDetails(payload.amazonOrderId);
      break;
    case 'InventoryLevels':
      await updateInventoryCache(payload);
      break;
  }
}
Enter fullscreen mode Exit fullscreen mode

Limites de Taxa e Cotas

Entendendo os Limites de Taxa

Categoria de Endpoint Limite de Taxa Limite de Pico
Pedidos 10 req/s 20
Itens do Pedido 5 req/s 10
Estoque 2 req/s 5
Listagens 10 req/s 20
Relatórios 0,5 req/s 1
Notificações 1 req/s 2
FBA Entrada 2 req/s 5

Verifique o cabeçalho x-amzn-RateLimit-Limit nas respostas.

Implementando o Tratamento de Limite de Taxa

const makeRateLimitedRequest = async (method, endpoint, accessToken, body = null, maxRetries = 5) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await makeSpApiRequest(method, endpoint, accessToken, body);
      const rateLimit = response.headers.get('x-amzn-RateLimit-Limit');
      const retryAfter = response.headers.get('Retry-After');
      if (retryAfter) {
        console.warn(`Rate limited. Retry after: ${retryAfter} seconds`);
      }
      return response;
    } catch (error) {
      if (error.message.includes('429') && attempt < maxRetries) {
        const retryAfter = error.headers?.get('Retry-After') || Math.pow(2, attempt);
        console.log(`Rate limited. Retrying in ${retryAfter}s...`);
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
      } else if (error.message.includes('503') && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000;
        console.log(`Service unavailable. Retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      } else {
        throw error;
      }
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Implementação de Fila de Requisições

class RateLimitedQueue {
  constructor(rateLimit, burstLimit = null) {
    this.rateLimit = rateLimit;
    this.burstLimit = burstLimit || rateLimit * 2;
    this.tokens = this.burstLimit;
    this.lastRefill = Date.now();
    this.queue = [];
    this.processing = false;
  }
  async add(requestFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ requestFn, resolve, reject });
      this.process();
    });
  }
  refillTokens() {
    const now = Date.now();
    const elapsed = (now - this.lastRefill) / 1000;
    const tokensToAdd = elapsed * this.rateLimit;
    this.tokens = Math.min(this.burstLimit, this.tokens + tokensToAdd);
    this.lastRefill = now;
  }
  async process() {
    if (this.processing || this.queue.length === 0) return;
    this.processing = true;
    while (this.queue.length > 0) {
      this.refillTokens();
      if (this.tokens < 1) {
        const waitTime = (1 / this.rateLimit) * 1000;
        await new Promise(r => setTimeout(r, waitTime));
        continue;
      }
      this.tokens--;
      const { requestFn, resolve, reject } = this.queue.shift();
      try {
        const result = await requestFn();
        resolve(result);
      } catch (error) {
        reject(error);
      }
    }
    this.processing = false;
  }
}

// Exemplo: Fila da API de Pedidos (10 req/s)
const ordersQueue = new RateLimitedQueue(10, 20);
const orders = await ordersQueue.add(() => getOrders(accessToken, options));
Enter fullscreen mode Exit fullscreen mode

Melhores Práticas de Segurança

Gerenciamento de Credenciais

Nunca coloque credenciais no código-fonte. Use variáveis de ambiente ou um gerenciador de segredos:

// NÃO FAÇA
const AWS_ACCESS_KEY = 'AKIAIOSFODNN7EXAMPLE';
const AWS_SECRET = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY';

// FAÇA
const AWS_ACCESS_KEY = process.env.AWS_ACCESS_KEY_ID;
const AWS_SECRET = process.env.AWS_SECRET_ACCESS_KEY;
Enter fullscreen mode Exit fullscreen mode

Com esses passos práticos e exemplos, você está pronto para integrar a Amazon SP-API em produção, automatizando operações de pedidos, estoque, listagens, relatórios e notificações em larga escala.

Top comments (0)