Variantes de produto
As variantes são as versões vendáveis de um produto(/docs/guide/products/overview): cada uma carrega seu próprio preço, SKU e regras de cobrança. Esta página mostra como criar, listar, consultar, atua
As variantes são as versões vendáveis de um produto: cada uma carrega seu próprio preço, SKU e regras de cobrança. Esta página mostra como criar, listar, consultar, atualizar e excluir variantes — e por que o pricing define se a variante é cobrada uma vez (transação) ou de forma recorrente (assinatura).
| Operação | Método | Endpoint |
|---|---|---|
| Criar | POST | /v1/products/{productId}/variants |
| Atualizar | PATCH | /v1/products/{productId}/variants |
| Listar (de um produto) | GET | /v1/products/{productId}/variants |
| Listar todas, escopo da conta (array) | GET | /v1/variants |
| Listar todas (array enxuto) | GET | /v1/products/variants/listall |
| Consultar uma | GET | /v1/variants/{variantId} |
| Excluir | DELETE | /v1/variants/{variantId} |
Todas as chamadas usam a base https://api.selectwin.io/v1 e o header SelectKey (em produção sl_live_…, em sandbox sl_test_…). Nunca use Authorization: Bearer/Basic. Veja Autenticação.
Como funciona
Uma variante sempre pertence a um produto (productId) e é a unidade que realmente entra em uma cobrança. O objeto pricing segue o modelo de precificação de dois eixos, ambos imutáveis após a criação:
pricing.type— natureza da cobrança:oneTime(cobrada uma vez, em uma transação) ourecurring(assinatura). DefaultoneTime.pricing.schema— como o preço é calculado: hoje sóunit(preço por unidade).
Os campos de recorrência (billingType, billingFrequency, billingFrequencyCount, cycles, trialInterval, trialIntervalCount, billingExactDay) só fazem sentido quando type=recurring. Quando type=recurring, billingType, billingFrequency e billingFrequencyCount passam a ser obrigatórios. Veja a referência completa de campos em Visão geral de Produtos.
Imutabilidade
pricing.type e pricing.schema não podem ser alterados depois de criados. Para mudar a natureza ou o esquema de cobrança, crie uma nova variante e desative a antiga (enabled: false). Tentar alterá-los retorna pricingTypeImmutable ou pricingSchemaImmutable (422).
pricing.unitPrice é o preço; pricing.costPerItem é o custo
unitPrice (em centavos, 9900 = R$ 99,00) é o que o cliente paga. costPerItem (também em centavos, pode ser 0) é o custo do item (COGS) e serve de base para os relatórios de margem e lucro em analytics. O costPerItem só aparece na leitura individual (GET /v1/variants/{variantId}) e nas respostas de criar/atualizar — ele não vem nas variantes embutidas no produto nem na listagem por produto.
Quando usar
- Crie variantes sempre que um produto tiver mais de uma forma de venda (mensal, anual, tamanhos, planos) — cada preço/SKU é uma variante.
- Use
oneTimepara vendas avulsas (cobradas em uma transação) erecurringpara assinaturas. - Não tente reaproveitar uma variante mudando seu
type/schema— eles são imutáveis. Crie outra variante. - Para apenas ocultar uma variante sem perder o histórico, prefira
enabled: falseem vez de excluir.
Criar variantes
POST /v1/products/{productId}/variants
Cria uma variante sob o produto identificado por productId no caminho. A resposta sempre envolve o resultado em um array results, com um contador created.
Headers
| Header | Obrigatório | Valor |
|---|---|---|
SelectKey | sim | sl_live_… (produção) ou sl_test_… (sandbox) |
Content-Type | sim | application/json |
Parâmetros de caminho
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
productId | string | sim | ID do produto pai (ex.: prd_01hqzvabc) |
Corpo
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
name | string | sim | Nome da variante |
pricing | object | sim | Objeto de precificação (abaixo) |
description | string | não | Descrição curta |
sku | string | não | Stock keeping unit |
images | array | não | URLs de imagens (máx. 1) |
metadata | object | não | Metadados livres |
attributes | object | não | Pares chave-valor (máx. 20) |
externalReference | string | não | Referência no seu catálogo |
enabled | boolean | não | Se a variante está disponível para venda |
Campos de pricing:
| Campo | Tipo / enum | Obrigatório | Descrição |
|---|---|---|---|
unitPrice | integer, 500..200000000 | sim | Preço unitário em centavos |
currency | enum ["BRL"] | sim | Código ISO 4217 |
schema | enum ["unit"] | não | Esquema de cálculo. Hoje só unit. Imutável |
type | enum ["oneTime","recurring"] | não | Natureza da cobrança. Default oneTime. Imutável |
oldPrice | integer, 500..200000000 | não | Preço "de" (promocional), em centavos |
costPerItem | integer, 0..200000000 | não | Custo do item (COGS) em centavos. Pode ser 0 |
billingType | enum ["prepaid","postpaid","exactday"] | recurring | Tipo da cobrança recorrente. Obrigatório se type=recurring |
billingFrequency | enum ["daily","weekly","monthly","yearly"] | recurring | Unidade da cadência. Obrigatório se type=recurring |
billingFrequencyCount | integer, 1..999 | recurring | Multiplicador (ex.: monthly + 3 = trimestral). Obrigatório se type=recurring |
billingExactDay | integer, 1..31 | não | Dia fixo de cobrança, quando billingType=exactday |
cycles | integer, 1..999 | não | Total de ciclos. Omitir/null = infinito |
trialInterval | enum ["day","week","month","year"] | não | Unidade do período de teste (só recurring) |
trialIntervalCount | integer, 1..365 | não | Duração do trial (ex.: 2 + week = 2 semanas) |
Não envie daysTrial
daysTrial aparece apenas na leitura como a duração equivalente em dias (derivada de trialInterval + trialIntervalCount). Não a inclua ao criar ou atualizar — modele o trial sempre pelos dois campos trialInterval e trialIntervalCount.
Exemplo de uma variante de assinatura (recurring):
curl -X POST "https://api.selectwin.io/v1/products/prd_01hqzvabc/variants" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ" \
-H "Content-Type: application/json" \
-d '{
"name": "Plano Anual",
"description": "Acesso por 12 meses",
"sku": "SKU-001",
"images": ["https://example.com/variant.png"],
"metadata": { "tier": "pro" },
"attributes": {},
"externalReference": "VAR-EXT-1",
"enabled": true,
"pricing": {
"unitPrice": 9900,
"currency": "BRL",
"schema": "unit",
"type": "recurring",
"oldPrice": 14900,
"costPerItem": 4500,
"billingType": "prepaid",
"billingFrequency": "monthly",
"billingFrequencyCount": 1,
"billingExactDay": 10,
"cycles": 12,
"trialInterval": "day",
"trialIntervalCount": 7
}
}'Para uma variante de venda avulsa, use "type": "oneTime" e omita os campos de recorrência:
{
"name": "Licença vitalícia",
"pricing": { "unitPrice": 9900, "currency": "BRL", "schema": "unit", "type": "oneTime" }
}Resposta 201 Created
{
"status": "success",
"created": 1,
"results": [
{
"id": "var_01hqzvabc",
"name": "Plano Anual",
"enabled": true,
"description": "Yearly billing",
"sku": "SKU-001",
"pricing": {
"oldPrice": 14900,
"unitPrice": 9900,
"currency": "BRL",
"schema": "unit",
"type": "recurring",
"daysTrial": 7,
"billingType": "prepaid",
"billingFrequency": "monthly",
"billingFrequencyCount": 1,
"billingExactDay": 10,
"cycles": 12,
"trialInterval": "day",
"trialIntervalCount": 7,
"costPerItem": 4500
},
"images": ["https://cdn.selectwin.io/v/var.png"],
"metadata": { "tier": "pro" },
"externalReference": "VAR-EXT-1",
"attributes": {}
}
]
}| Campo | Descrição |
|---|---|
status | "success" em caso de êxito |
created | Quantidade de variantes criadas |
results[] | Variantes criadas, já com id gerado e pricing completo (inclui daysTrial e costPerItem) |
Atualizar variantes
PATCH /v1/products/{productId}/variants
Atualiza uma variante existente do produto. O corpo é o mesmo da criação, com um campo extra obrigatório: o id da variante a alterar.
Corpo (campos adicionais ao da criação)
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string, padrão ^[a-z]+_[A-Za-z0-9]+$ | sim | ID público da variante a atualizar (ex.: var_01hqzvabc) |
pricing.unitPrice e pricing.currency continuam obrigatórios dentro de pricing. Lembre-se: pricing.type e pricing.schema não podem ser alterados.
curl -X PATCH "https://api.selectwin.io/v1/products/prd_01hqzvabc/variants" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ" \
-H "Content-Type: application/json" \
-d '{
"id": "var_01hqzvabc",
"name": "Plano Anual (novo preço)",
"pricing": {
"unitPrice": 10900,
"currency": "BRL",
"type": "recurring",
"billingType": "prepaid",
"billingFrequency": "monthly",
"billingFrequencyCount": 1
}
}'Resposta 200 OK
{
"status": "success",
"updated": 1,
"results": [
{
"id": "var_01hqzvabc",
"name": "Plano Anual (novo preço)",
"enabled": true,
"pricing": {
"oldPrice": 14900,
"unitPrice": 10900,
"currency": "BRL",
"billingType": "prepaid",
"billingFrequency": "monthly",
"billingFrequencyCount": 1,
"costPerItem": 4500
},
"images": ["https://cdn.selectwin.io/v/var.png"],
"metadata": { "tier": "pro" },
"externalReference": "VAR-EXT-1",
"attributes": {}
}
]
}O contador agora é updated (em vez de created).
Listar variantes
Há três formas de listar, com finalidades diferentes.
De um produto — GET /v1/products/{productId}/variants (paginada)
Retorna as variantes de um produto, com paginação e filtros. Veja Paginação para o significado de offset, limit, hasMore e page.
Parâmetros de query
| Nome | Tipo / enum | Descrição |
|---|---|---|
limit | number, 1..100 | Itens por página (ex.: 20) |
offset | number, 0.. | Quantos itens pular |
sort | enum ["ascending","descending"] | Ordem dos resultados |
sku | string | Filtra por SKU |
currency | enum ["BRL"] | Filtra pela moeda do preço |
enabled | boolean | Filtra pelo flag de disponibilidade |
daterange | string (ISO 8601) | Restringe a um único dia civil |
daterangegt / daterangegte | string (ISO 8601) | Limite inferior, exclusivo / inclusivo |
daterangelt / daterangelte | string (ISO 8601) | Limite superior, exclusivo / inclusivo |
curl "https://api.selectwin.io/v1/products/prd_01hqzvabc/variants?limit=20&enabled=true" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ"{
"offset": 0,
"limit": 20,
"total": 2,
"page": {
"offset": { "first": 0, "prev": null, "next": null, "last": 0 },
"current": 1,
"total": 1
},
"data": [
{
"id": "var_01hqzvabc",
"name": "Mensal",
"enabled": true,
"description": "Monthly billing",
"sku": "SKU-M",
"pricing": {
"oldPrice": 14900,
"unitPrice": 9900,
"currency": "BRL",
"schema": "unit",
"type": null,
"daysTrial": 7,
"billingType": null,
"billingFrequency": null,
"billingFrequencyCount": null,
"billingExactDay": null,
"cycles": null,
"trialInterval": null,
"trialIntervalCount": null
},
"images": ["https://cdn.selectwin.io/v/var.png"],
"metadata": { "tier": "pro" },
"externalReference": "VAR-EXT-1",
"attributes": {},
"updatedAt": "2026-04-12T17:56:33.000Z",
"createdAt": "2026-04-12T17:56:33.000Z"
}
],
"hasMore": false
}Nesta projeção o objeto
pricingnão incluicostPerItem— use a consulta individual quando precisar do custo.
Todas — GET /v1/variants (array enxuto, escopo da conta)
Lista variantes em escopo global da conta (não preso a um produto). A resposta é o mesmo array direto e enxuto do listall — campos id, name, enabled, images, description, sku, o preço no campo plano price (em centavos), updatedAt e createdAt. Não há envelope de paginação (offset/limit/total/page/data/hasMore), nem objeto pricing, nem productId na resposta.
A diferença real para o listall está só nos filtros: aqui você pode restringir por id e productId (ambos no padrão ^[a-z]+_[A-Za-z0-9]+$), além dos filtros acima. O limit aceita até 999999.
curl "https://api.selectwin.io/v1/variants?productId=prd_01hqzvabc&sort=descending" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ"[
{
"id": "var_01hqzvabc",
"name": "Mensal",
"enabled": true,
"images": ["https://example.com/variant.png"],
"description": "Monthly billing",
"sku": "SKU-M",
"price": 9900,
"updatedAt": "2026-04-12T17:56:33.000Z",
"createdAt": "2026-04-12T17:56:33.000Z"
}
]Todas — GET /v1/products/variants/listall (array enxuto)
Retorna um array direto (sem envelope de paginação) com campos resumidos. Aqui o preço é o campo plano price (em centavos) — não há objeto pricing, type nem productId. Útil para preencher seletores/menus rapidamente.
curl "https://api.selectwin.io/v1/products/variants/listall?enabled=true" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ"[
{
"id": "var_01hqzvabc",
"name": "Mensal",
"enabled": true,
"images": ["https://example.com/variant.png"],
"description": "Monthly billing",
"sku": "SKU-M",
"price": 9900,
"updatedAt": "2026-04-12T17:56:33.000Z",
"createdAt": "2026-04-12T17:56:33.000Z"
}
]| Listagem | Envelope | Preço | Inclui costPerItem / productId |
|---|---|---|---|
/products/{productId}/variants | paginado | objeto pricing | não / não |
/variants | array direto | campo plano price | não / não |
/products/variants/listall | array direto | campo plano price | não / não |
Consultar uma variante
GET /v1/variants/{variantId}
Lê uma variante pelo ID. É a única leitura que traz o objeto pricing completo, incluindo costPerItem, além de productId.
curl "https://api.selectwin.io/v1/variants/var_01hqzvabc" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ"{
"id": "var_01hqzvabc",
"productId": "prd_01hqzvabc",
"name": "Mensal",
"enabled": true,
"description": "Monthly billing",
"sku": "SKU-M",
"pricing": {
"oldPrice": 14900,
"unitPrice": 9900,
"costPerItem": 5000,
"currency": "BRL",
"schema": "unit",
"type": "recurring",
"daysTrial": 7,
"billingType": "prepaid",
"billingFrequency": "monthly",
"billingFrequencyCount": 1,
"billingExactDay": 10,
"cycles": 12,
"trialInterval": "day",
"trialIntervalCount": 7
},
"images": ["https://cdn.selectwin.io/v/var.png"],
"metadata": { "tier": "pro" },
"externalReference": "VAR-EXT-1",
"attributes": null,
"updatedAt": "2026-04-12T17:56:33.000Z",
"createdAt": "2026-04-12T17:56:33.000Z"
}Excluir uma variante
DELETE /v1/variants/{variantId}
Exclui a variante. A operação é definitiva — confirme antes que nenhuma assinatura ativa ou oferta dependa dela. Se a variante ainda estiver em uso, prefira desativá-la com enabled: false via atualização em vez de excluir.
curl -X DELETE "https://api.selectwin.io/v1/variants/var_01hqzvabc" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ"Resposta 200 OK
{
"id": "var_01hqzvabc",
"resource": "variant",
"deleted": true
}| Campo | Descrição |
|---|---|
id | ID da variante excluída |
resource | Tipo do recurso ("variant") |
deleted | true quando a exclusão é confirmada |
Erros
error.code | HTTP | Quando ocorre |
|---|---|---|
variantIdNotFound | 404 | Variante inexistente |
variantIdIsInvalid / productVariantIdIsInvalid | 400 | ID de variante inválido |
productVariantIdIsRequired | 400 | Faltou o ID da variante (ex.: id no PATCH) |
productIdIsInvalid | 400 | productId do caminho inválido |
productIdNotFound | 404 | Produto pai não encontrado |
pricingTypeImmutable | 422 | Tentou alterar pricing.type |
pricingSchemaImmutable | 422 | Tentou alterar pricing.schema |
variantRecurringNotAllowedInTransaction | 422 | Variante recurring usada em transação avulsa |
oneTimeVariantNotAllowedInSubscription | 422 | Variante oneTime usada em assinatura |
Erros transversais que valem para todos os endpoints: 401 unauthorized, 403 forbidden, 422 unprocessableEntity (corpo inválido), 429 tooManyRequests, 500 serverError. Veja o catálogo completo em Códigos de Erro e o formato do envelope em Erros.
Boas práticas
- Use centavos, sempre.
unitPrice,oldPriceecostPerItemsão inteiros em centavos (9900= R$ 99,00). Veja Convenções. - Defina
type/schemacertos na criação — são imutáveis. Para mudar, crie outra variante e desative a antiga. - Modele o trial com
trialInterval+trialIntervalCount; não enviedaysTrial(é só leitura). - Para
recurring, sempre informebillingType,billingFrequencyebillingFrequencyCount— sem eles a criação falha. - Prefira
enabled: falsea excluir quando a variante tem histórico ou está em assinaturas ativas — a exclusão é definitiva. - Escolha a listagem certa:
listall(array compriceplano) para menus rápidos;GET /v1/variants/{variantId}quando precisar decostPerItem. - Use
externalReferencepara casar a variante com o ID do seu próprio catálogo e evitar duplicatas.
Veja também
- Visão geral de Produtos — modelo do objeto e precificação de dois eixos
- Criar produto · Consultar produto · Atualizar produto · Listar produtos
- Convenções · Paginação · Códigos de Erro
How is this guide?