@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
- Instalação
- Autenticação OAuth 2.0
- Inicializando o cliente
- Resources disponíveis
- Paginação
- Rate Limiting
- Webhooks
- Tratamento de erros
- Build e configuração
Requisitos
- Node.js >= 18.0.0
- TypeScript >= 5.4
Instalação
npm install sdk-nuvemshop-apiAutenticaçã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 autorizadosImportante: 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ãoAuthorization) 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 bucketComportamento:
- Antes de cada requisição,
throttleIfNeeded()é chamado internamente. - Se
remaining ≤ 1, o SDK aguarda o tempo necessário (resetms) antes de prosseguir. - Isso previne erros
429 Too Many Requestsna 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 typecheckO build gera os arquivos em ./dist/ com declarations (.d.ts), declaration maps e source maps habilitados.
Configuração TypeScript (strict):
target: ES2022,module: NodeNextstrict,noUncheckedIndexedAccess,noImplicitOverride,exactOptionalPropertyTypes
