npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@liderautomacao/sdk-nuvemshop-api

v1.0.1

Published

TypeScript SDK for the Nuvemshop REST API with built-in leaky-bucket rate-limit support

Readme

sdk-nuvemshop-api

SDK TypeScript para a API REST da Nuvemshop, com suporte nativo a OAuth 2.0, rate limiting automático via leaky bucket, paginação lazy e validação de webhooks.


Índice


Requisitos

  • Node.js >= 18.0.0
  • TypeScript >= 5.4

Instalação

npm install sdk-nuvemshop-api

Autenticação OAuth 2.0

A Nuvemshop usa o fluxo Authorization Code do OAuth 2.0. O SDK encapsula os dois passos do fluxo.

Passo 1 — Redirecionar o lojista para a tela de autorização

import { NuvemshopAuth } from "sdk-nuvemshop-api";

const url = NuvemshopAuth.getAuthorizationUrl(
  process.env.APP_CLIENT_ID,
  "https://meu-app.com/auth/callback", // redirect_uri (opcional)
  "estado-csrf-aleatorio", // state (opcional, recomendado)
);

// Redirecione o lojista para `url`
res.redirect(url);

Passo 2 — Trocar o código temporário pelo token de acesso

import { NuvemshopAuth } from "sdk-nuvemshop-api";

// No handler do callback:
const token = await NuvemshopAuth.exchangeCodeForToken(
  process.env.APP_CLIENT_ID,
  process.env.APP_CLIENT_SECRET,
  req.query.code, // code recebido via query string pela Nuvemshop
);

// Persista esses valores no banco:
console.log(token.access_token); // token permanente
console.log(token.user_id); // ID da loja (storeId)
console.log(token.scope); // escopos autorizados

Importante: A Nuvemshop exige que os parâmetros da troca de token sejam enviados no body JSON, não em query string. O SDK já trata isso corretamente.


Inicializando o cliente

import { NuvemshopClient } from "sdk-nuvemshop-api";

const client = new NuvemshopClient({
  storeId: token.user_id, // ID numérico da loja
  accessToken: token.access_token,
  appName: "Meu Aplicativo", // Obrigatório no User-Agent
  appEmail: "[email protected]", // Obrigatório no User-Agent
});

O cliente define automaticamente em todas as requisições:

| Header | Valor | | ---------------- | ------------------------ | | Authentication | bearer {accessToken} | | User-Agent | {appName} ({appEmail}) | | Content-Type | application/json |

Atenção: A Nuvemshop usa Authentication (e não Authorization) como nome do header de autenticação.


Resources disponíveis

Products

Acesse via client.products.

Listar produtos

Retorna um paginador lazy compatível com for await...of. Cada página é buscada sob demanda.

const paginator = await client.products.listProducts({
  per_page: 200, // máximo permitido pela API (default: 200)
  published: true,
  updated_at_min: "2024-01-01T00:00:00Z",
});

console.log(`Total de produtos: ${paginator.totalCount}`);

for await (const product of paginator) {
  console.log(product.id, product.name);
}

Parâmetros disponíveis:

| Parâmetro | Tipo | Descrição | | ---------------- | ------- | ------------------------------------- | | page | number | Número da página | | per_page | number | Itens por página (máx 200) | | since_id | number | Retornar apenas IDs maiores que este | | language | string | Idioma dos campos traduzíveis | | q | string | Busca textual | | handle | string | Filtrar por handle (slug) | | category_id | number | Filtrar por categoria | | published | boolean | Apenas publicados / não publicados | | free_shipping | boolean | Filtrar por frete grátis | | created_at_min | string | Data de criação mínima (ISO 8601) | | created_at_max | string | Data de criação máxima (ISO 8601) | | updated_at_min | string | Data de atualização mínima (ISO 8601) | | updated_at_max | string | Data de atualização máxima (ISO 8601) |

Atualizar estoque de variantes

Suporte a múltiplos centros de distribuição via location_id.

// Substituição direta (estoque exato)
await client.products.updateVariantStock(productId, {
  id: variantId,
  action: "replace",
  value: 50,
  location_id: 123, // opcional: CD específico
});

// Ajuste relativo (delta positivo ou negativo)
await client.products.updateVariantStock(productId, {
  id: variantId,
  action: "variation",
  value: -3, // subtrai 3 unidades
});

Fulfillment (Logística)

Acesse via client.orders.

Buscar fulfillment orders de um pedido

const fulfillments = await client.orders.getFulfillmentOrders(orderId);

for (const fulfillment of fulfillments) {
  console.log(fulfillment.id);
  console.log(fulfillment.status);
  console.log(fulfillment.shipping.address.city);
  console.log(fulfillment.tracking_info?.code);
}

Registrar evento de rastreamento

await client.orders.createTrackingEvent(orderId, fulfillmentId, {
  status: "in_transit",
  description: "Objeto em trânsito para cidade destino",
  notify_customer: true, // envia notificação ao comprador (opcional)
});

Status disponíveis:

| Status | Descrição | | ------------------ | ------------------------- | | collected | Objeto coletado / postado | | in_transit | Em trânsito | | out_for_delivery | Saiu para entrega | | delivered | Entregue | | returned | Devolvido | | lost | Extraviado |


Metafields (NF-e)

Acesse via client.metafields.

Vincular NF-e a um pedido

O método realiza upsert automático: cria o metafield se não existir, ou adiciona a nova NF-e ao array já existente.

await client.metafields.appendNfeToOrder(
  orderId,
  "12345678901234567890123456789012345678901234", // chave de 44 dígitos
  "https://nfe.fazenda.gov.br/portal/consultaRecaptcha.aspx?...",
  fulfillmentOrderId, // opcional
);

O valor é armazenado na API como JSON string com a estrutura:

[
  {
    "key": "12345678901234567890123456789012345678901234",
    "link": "https://nfe.fazenda.gov.br/...",
    "fulfillment_order_id": 789
  }
]

Paginação

O SDK implementa paginação lazy via AsyncIterator. As páginas são buscadas sob demanda conforme você itera, respeitando o rate limit automaticamente entre páginas.

const paginator = await client.products.listProducts({ per_page: 200 });

// Total de registros (header x-total-count da primeira página)
console.log(paginator.totalCount);

// Iterar página por página de forma automática
for await (const product of paginator) {
  // cada `product` é um item individual — o SDK cuida da troca de páginas
  await processarProduto(product);
}

Rate Limiting

O cliente implementa um leaky bucket que lê os headers x-rate-limit-* após cada resposta e pausa automaticamente quando o bucket está quase vazio (remaining ≤ 1).

// Verificar o estado atual do rate limit
const state = client.rateLimitState;
console.log(state.limit); // capacidade total do bucket
console.log(state.remaining); // requisições disponíveis
console.log(state.reset); // ms até o reset do bucket

Comportamento:

  • Antes de cada requisição, throttleIfNeeded() é chamado internamente.
  • Se remaining ≤ 1, o SDK aguarda o tempo necessário (reset ms) antes de prosseguir.
  • Isso previne erros 429 Too Many Requests na prática sem nenhuma configuração adicional.

Webhooks

A Nuvemshop assina cada entrega de webhook com HMAC-SHA256 via o header x-linkedstore-hmac-sha256.

Validar assinatura (lança exceção se inválida)

import { NuvemshopWebhookValidator } from "sdk-nuvemshop-api";

// Exemplo com Express — use express.raw() para preservar o body bruto
app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
  NuvemshopWebhookValidator.validate(
    process.env.APP_CLIENT_SECRET,
    req.body, // Buffer bruto — não faça JSON.parse antes
    req.headers["x-linkedstore-hmac-sha256"] as string,
  );

  // Responda em até 3 segundos — a Nuvemshop agenda até 18 retentativas em caso de falha
  res.sendStatus(200);

  // Processamento assíncrono após o 200
  processarEventoAsync(JSON.parse(req.body.toString())).catch(console.error);
});

Verificar sem lançar exceção

const valido = NuvemshopWebhookValidator.isValid(
  process.env.APP_CLIENT_SECRET,
  rawBody,
  signatureHeader,
);

if (!valido) {
  return res.status(401).send("Assinatura inválida");
}

Segurança: A comparação usa crypto.timingSafeEqual, que garante tempo de execução constante independentemente de onde as strings divergem, prevenindo ataques de canal lateral baseados em timing.

SLA: A Nuvemshop espera HTTP 2xx em até 3 segundos. Respostas fora do prazo resultam em até 18 retentativas automáticas.


Tratamento de erros

Todas as exceções estendem NuvemshopError e expõem .statusCode, .message e .response (body raw da API).

import {
  NuvemshopPaymentRequiredError,
  NuvemshopRateLimitError,
  NuvemshopUnprocessableEntityError,
  NuvemshopWebhookSignatureError,
  NuvemshopOAuthError,
} from "sdk-nuvemshop-api";

try {
  await client.products.listProducts();
} catch (err) {
  if (err instanceof NuvemshopPaymentRequiredError) {
    // HTTP 402 — loja com fatura vencida, API bloqueada
    console.error("Loja inadimplente:", err.message);
  } else if (err instanceof NuvemshopRateLimitError) {
    // HTTP 429 — rate limit excedido (raro com o throttle automático)
    console.error("Rate limit atingido:", err.message);
  } else if (err instanceof NuvemshopUnprocessableEntityError) {
    // HTTP 422 — payload inválido
    console.error("Erro de validação:", err.details);
  } else if (err instanceof NuvemshopWebhookSignatureError) {
    // Assinatura HMAC inválida
    console.error("Assinatura recebida:", err.receivedSignature);
  } else if (err instanceof NuvemshopOAuthError) {
    // Falha no fluxo OAuth
    console.error("Erro OAuth:", err.statusCode, err.response);
  }
}

Resumo das exceções:

| Classe | HTTP | Quando ocorre | | ----------------------------------- | -------- | ------------------------------------------ | | NuvemshopPaymentRequiredError | 402 | Loja com fatura vencida | | NuvemshopRateLimitError | 429 | Rate limit excedido | | NuvemshopUnprocessableEntityError | 422 | Payload inválido (expõe .details) | | NuvemshopWebhookSignatureError | — | HMAC inválido (expõe .receivedSignature) | | NuvemshopOAuthError | variável | Qualquer falha no fluxo OAuth |


Build e configuração

# Compilar o projeto
npm run build

# Verificar tipos sem emitir arquivos
npm run typecheck

O build gera os arquivos em ./dist/ com declarations (.d.ts), declaration maps e source maps habilitados.

Configuração TypeScript (strict):

  • target: ES2022, module: NodeNext
  • strict, noUncheckedIndexedAccess, noImplicitOverride, exactOptionalPropertyTypes