Produtos e Variantes - Visão Geral
Introdução
O recurso de Produtos (Products) representa seu catálogo. Cada produto agrupa uma ou mais Variantes (Variants) — e é a variante que carrega a precificação. Um produto "Plano Premium" pode ter as variantes "Mensal", "Anual" e "Vitalício", cada uma com seu preço e regras de cobrança.
As variantes são o que você referencia em Transações (variantes oneTime)
e em Assinaturas (variantes recurring).
Estrutura do Produto
{
"id": "prd_01hqzvabc",
"name": "Plano Premium",
"slug": "plano-premium",
"salesPage": "https://example.com/sales",
"description": "Full access plan",
"language": "pt-BR",
"enabled": true,
"salesQty": 42,
"type": "digital",
"category": "saas",
"externalReference": "SKU-EXT-1",
"warrantyDays": 7,
"images": ["https://example.com/img1.png"],
"imageAssetIds": null,
"createdAt": "2026-04-12T17:56:33.000Z",
"updatedAt": "2026-04-12T17:56:33.000Z",
"variants": [
{
"id": "var_01hqzvabc",
"productId": "prd_01hqzvabc",
"name": "Mensal",
"slug": "mensal",
"description": "Monthly billing",
"sku": "SKU-M",
"primary": true,
"enabled": true,
"pricing": {
"unitPrice": 9900,
"oldPrice": null,
"costPerItem": null,
"currency": "BRL",
"schema": "unit",
"type": "recurring",
"billingType": "prepaid",
"billingFrequency": "monthly",
"billingFrequencyCount": 1
},
"images": null,
"metadata": null,
"attributes": null,
"externalReference": null,
"checkoutUrl": null,
"createdAt": "2026-04-12T17:56:33.000Z",
"updatedAt": "2026-04-12T17:56:33.000Z"
}
]
}Campos do produto
| Campo | Tipo | Descrição |
|---|---|---|
id | string | Identificador público (prd_...; ids legados pro_ também são lidos) |
name | string | Nome do produto |
slug | string | null | Identificador legível para URLs (gerado pela API a partir do name) |
salesPage | string | null | Página de vendas |
description | string | null | Descrição |
language | string | null | Idioma (pt-BR, …) |
enabled | boolean | Se o produto está ativo. Desligado, o produto não vende: suas variantes deixam de resolver no carrinho do checkout, num link de pagamento novo e numa assinatura nova (422 variantIdIsInvalid / 404 variantNotFound), mesmo com a variante ligada. As variantes guardam o próprio enabled: ao religar o produto, voltam como estavam. Assinaturas já contratadas continuam cobrando |
salesQty | integer | null | Quantidade vendida (somente leitura) |
type | string | Tipo: physical, digital ou other |
category | string | Categoria de negócio |
externalReference | string | null | Sua referência externa |
warrantyDays | integer | Dias de garantia |
images | array | null | URLs de imagens |
imageAssetIds | qualquer | null | Referências internas das imagens hospedadas |
fiscal | object | null | O perfil fiscal (NCM, CEST, GTIN, origem, unidade, LC 116, NBS…) — ver Criar |
createdAt / updatedAt | string (ISO 8601) | Datas |
variants | array | Variantes do produto (com pricing) |
Modelo de Precificação (dual-axis)
A precificação vive na variante, em pricing, organizada em dois eixos independentes:
Eixo 1 — type (natureza da cobrança) · imutável
pricing.type | Significado | Onde é usada |
|---|---|---|
oneTime | Cobrança única (default) | Transações |
recurring | Cobrança recorrente | Assinaturas |
- Uma variante
oneTimenão pode ser usada em assinatura (a assinatura exige variante recorrente) →variantNotRecurring(422). - O
typeé imutável após a criação →pricingTypeImmutable(422).
Eixo 2 — schema (como o preço é calculado) · imutável
pricing.schema | Significado |
|---|---|
unit | Preço por unidade — unitPrice × quantity (default) |
tiered | Preço por faixas (tiersMode + tiers[]) |
package | Preço por pacote (transformQuantity) |
Campos de pricing
| Campo | Aplica a | Descrição |
|---|---|---|
unitPrice | ambos | Preço unitário em centavos (mín. 1; em schema=unit, mín. 500). Obrigatório exceto em schema=tiered |
oldPrice | ambos | Preço "de" (riscado/promocional), em centavos |
costPerItem | ambos | Custo do item em centavos (para margem; mín. 0) |
currency | ambos | ISO 4217 — apenas BRL (default BRL) |
schema | ambos | unit | tiered | package (default unit, imutável) |
type | ambos | oneTime | recurring (default oneTime, imutável) |
daysTrial | recurring | Dias de trial (1–365) |
billingType | recurring | prepaid, postpaid ou exactday (obrigatório quando type=recurring) |
billingFrequency | recurring | daily, weekly, monthly, yearly (obrigatório quando type=recurring) |
billingFrequencyCount | recurring | Multiplicador da frequência, 1–999 (obrigatório quando type=recurring) |
billingExactDay | recurring | Dia fixo de cobrança, 1–31 (obrigatório quando billingType=exactday) |
cycles | recurring | Nº de ciclos, 1–999 (null = indefinido) |
trialInterval | recurring | Unidade do trial: day, week, month, year |
trialIntervalCount | recurring | Duração do trial em unidades de trialInterval (1–365) |
tiersMode | tiered | graduated ou volume (obrigatório quando schema=tiered) |
tiers | tiered | Faixas { upTo, unitPrice, flatPrice } (obrigatório quando schema=tiered) |
transformQuantity | package | { divideBy, round } (obrigatório quando schema=package) |
usageType | recurring | licensed (default) ou metered |
usageAggregation | metered | sum, max, lastDuringPeriod, lastEver (obrigatório quando usageType=metered) |
meterEventName | metered | Nome do evento de medição (obrigatório quando usageType=metered) |
renewalShipping | recurring | Cobrar o frete em cada renovação (default true). Ver regras da renovação |
renewalMethodDiscount | recurring | Estender o desconto do método de pagamento às renovações (default false). Ver regras da renovação |
Na resposta, pricing traz todos os campos acima (os não aplicáveis vêm null); unitPrice,
oldPrice e costPerItem sempre presentes (centavos ou null). renewalShipping e renewalMethodDiscount vêm
sempre como booleano: a regra que vale para o plano (a sua escolha, ou o default).
Regras da renovação
Num plano recorrente vendido pelo checkout hospedado, a 1ª cobrança é o total do pedido: itens + frete − cupons − desconto do método de pagamento (Pix, boleto ou cartão, conforme o template do checkout). As cobranças seguintes (as renovações) levam os itens − os cupons recorrentes e mais o que estas duas regras do plano dizem:
| Regra | Default | Com true | Com false |
|---|---|---|---|
renewalShipping | true | O frete cotado no checkout entra em cada renovação (quando a compra teve frete) | O frete fica só na 1ª cobrança |
renewalMethodDiscount | false | O desconto do método vale em cada renovação paga pelo mesmo método (uma renovação no cartão não leva o desconto do Pix) | O desconto fica só na 1ª cobrança |
- Por plano. Cada variante recorrente tem as suas regras: um plano físico e um digital da mesma loja podem ter
regras diferentes. Num plano digital, sem frete,
renewalShippingnão muda nada. - A assinatura congela as regras. No momento da compra, a assinatura guarda as duas regras, o frete cotado e o
percentual do desconto (o
renewalTermsdela). Mudar o plano depois vale só para quem assinar depois. - O checkout mostra o valor de cada renovação já com essas regras, antes do comprador pagar.
- Assinaturas criadas antes de 15/10/2026, pela API de assinaturas ou por um upsell não congelam regras: as renovações delas levam os itens − os cupons recorrentes.
Recursos Disponíveis
| Recurso | Método | Endpoint | Descrição |
|---|---|---|---|
| Criar produto | POST | /v1/products | Cria produto (com variantes inline) |
| Listar produtos | GET | /v1/products · /v1/products/listall | Lista paginada · array completo |
| Consultar produto | GET | /v1/products/{productId} | Produto + variantes |
| Atualizar / Excluir | PUT/PATCH/DELETE | /v1/products/{productId} | Atualiza ou remove |
| Variantes | GET/POST/PATCH/DELETE | /v1/products/{id}/variants · /v1/variants | Gerencia variantes |
Integração com Outros Recursos
- Transactions — variantes
oneTimesão cobradas em transações avulsas. - Subscriptions — variantes
recurringcompõem os itens da assinatura. - Coupons — descontos podem ser limitados a produtos/variantes específicos.
- Checkouts — links de pagamento referenciam variantes por
id.
Exportar produtos
Use POST /v1/exports — job assíncrono com dedup, TTL e download assinado.
O antigo POST /v1/products/export foi APOSENTADO e responde 410 apontando para o substituto. Ele
nunca gerou arquivo nenhum: devolvia { succeeded: true } a partir de uma função sem argumentos e sem saída
— um controle que dizia existir. A rota continua registrada de propósito, para o 410 nomear o substituto em
vez de o gateway devolver um 404 mudo.
Atualizado em 21 de outubro de 2026