Produtos
O produto é o catálogo puro: nome + preço mensal, fixo ou variável. Período, parcelamento e desconto não moram aqui — são decisões de cada plano. Modelo completo em Conceitos.
| Rota | O que faz |
|---|---|
POST /v1/products |
Cria um produto, com preço fixo ou variável |
GET /v1/products |
Lista o catálogo, paginado |
GET /v1/products/{productId} |
Um produto pelo id |
PATCH /v1/products/{productId} |
Edita nome, descrição ou preço |
POST /v1/products/{productId}/archive |
Bloqueia o produto em planos novos |
POST /v1/products/{productId}/unarchive |
Volta a aceitar o produto em planos novos |
POST /v1/products/{productId}/pricing-test |
Chamada real de teste no seu endpoint de preço |
O objeto Produto
Seção intitulada “O objeto Produto”Toda rota de produto devolve este objeto:
| Campo | Tipo | Descrição |
|---|---|---|
id |
string (uuid) | Identificador do produto no motor |
name |
string | Nome do produto — é o que o assinante lê na fatura |
description |
string | null | Descrição interna, opcional |
externalRef |
string | null | O seu id para este produto — devolvido nos webhooks e no endpoint de preço |
pricingMode |
enum | Como o preço é definido — ver valores. Imutável após a criação |
monthlyPriceCents |
integer | null | Preço mensal em centavos (24990 = R$ 249,90). null em produto variável |
endpointUrl |
string | null | URL do seu endpoint de preço. null em produto fixo |
capCents |
integer | null | Teto por cobrança em centavos — resposta do endpoint acima disso é falha. null em produto fixo |
archivedAt |
datetime | null | Quando foi arquivado — null = ativo. Arquivado não entra em plano novo |
createdAt |
datetime | Quando foi criado |
O enum pricingMode
Seção intitulada “O enum pricingMode”| Valor | Significado |
|---|---|
fixed |
Preço de tabela: o valor mensal está em monthlyPriceCents |
endpoint |
Preço variável: a cada ciclo o motor pergunta o valor ao seu sistema — veja o guia do endpoint de preço |
O preço é sempre o mensal; quem multiplica é o periodMonths do plano. Um plano semestral cobra 6 × o mensal numa fatura só.
Criar produto
Seção intitulada “Criar produto”POST /v1/productsHeaders:
| Header | Obrigatório | Descrição |
|---|---|---|
Idempotency-Key |
Sim | Chave única sua — repetir devolve a mesma resposta |
Corpo:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
name |
string | Sim | 1–120 caracteres. É o que o assinante lê na fatura |
description |
string | Não | Até 500 caracteres. Descrição interna |
externalRef |
string | Não | 1–120 caracteres. O seu id — devolvido em webhooks e no endpoint de preço |
pricing |
object | Sim | Uma das duas formas abaixo. O modo escolhido é imutável |
pricing com preço fixo:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
pricing.mode |
fixed |
Sim | Literal "fixed" |
pricing.monthlyPriceCents |
integer | Sim | Preço mensal em centavos, ≥ 0 |
pricing com preço variável:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
pricing.mode |
endpoint |
Sim | Literal "endpoint" |
pricing.url |
string | Sim | URL https do seu endpoint de preço |
pricing.capCents |
integer | Sim | Teto por cobrança em centavos, ≥ 0 — a proteção contra um bug seu virar uma cobrança absurda |
# preço fixocurl -X POST $MOTOR/products -H "$AUTH" -H "Idempotency-Key: prod-caixa-1" \ -H "Content-Type: application/json" -d '{ "name": "Caixa do Clube", "description": "6 garrafas · seleção do sommelier", "pricing": { "mode": "fixed", "monthlyPriceCents": 24990 } }'
# preço variávelcurl -X POST $MOTOR/products -H "$AUTH" -H "Idempotency-Key: prod-garrafas-1" \ -H "Content-Type: application/json" -d '{ "name": "Garrafas extras", "externalRef": "garrafas-extras", "pricing": { "mode": "endpoint", "url": "https://seusistema.com.br/pricing", "capCents": 100000 } }'Resposta 201 — o objeto Produto:
{ "id": "0199c1a2-…", "name": "Caixa do Clube", "description": "6 garrafas · seleção do sommelier", "externalRef": null, "pricingMode": "fixed", "monthlyPriceCents": 24990, "endpointUrl": null, "capCents": null, "archivedAt": null, "createdAt": "2026-08-29T18:12:03.000Z"}Se o seu gateway tem catálogo de produtos (Vindi, Stripe), o motor cria o espelho lá automaticamente, em background — a criação aqui nunca espera chamada a terceiro.
Erros: 400 (payload inválido ou Idempotency-Key ausente).
Listar produtos
Seção intitulada “Listar produtos”GET /v1/productsQuery (paginação padrão):
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
page |
integer | 1 |
Página, começando em 1 |
pageSize |
integer | 25 |
Itens por página, máximo 100 |
Resposta 200: { data: [Produto…], pagination }, mais recentes primeiro. Inclui os arquivados (com archivedAt preenchido).
Detalhar produto
Seção intitulada “Detalhar produto”GET /v1/products/{productId}| Parâmetro | Em | Descrição |
|---|---|---|
productId |
path | Id do produto |
Resposta 200: o objeto Produto. Erros: 404.
Editar produto
Seção intitulada “Editar produto”PATCH /v1/products/{productId}Todos os campos são opcionais — envie só o que muda:
| Campo | Tipo | Vale para | Descrição |
|---|---|---|---|
name |
string | ambos os modos | 1–120 caracteres |
description |
string | ambos os modos | Até 500 caracteres |
monthlyPriceCents |
integer | só produto fixed |
Novo preço mensal em centavos, ≥ 0 |
endpointUrl |
string | só produto endpoint |
Nova URL https do endpoint |
capCents |
integer | só produto endpoint |
Novo teto por cobrança, ≥ 0 |
Duas regras:
- Preço novo vale a partir das próximas faturas — nada retroativo é reprocessado.
- O modo de preço é imutável. Enviar campo do outro modo (ex.:
monthlyPriceCentsnum produto variável) devolve409— para trocar o modo, crie outro produto.
Resposta 200: o objeto Produto atualizado. Erros: 404, 409.
Arquivar e reativar
Seção intitulada “Arquivar e reativar”POST /v1/products/{productId}/archivePOST /v1/products/{productId}/unarchiveArquivar bloqueia o produto em planos novos; planos e assinaturas existentes seguem faturando normalmente. Produto nunca é apagado. Reativar volta a aceitá-lo em planos novos.
Resposta 200: o objeto Produto, com archivedAt preenchido (ou null de volta). Erros: 404.
Testar o endpoint de preço
Seção intitulada “Testar o endpoint de preço”POST /v1/products/{productId}/pricing-testSó para produto de preço variável. Dispara uma chamada real ao seu endpoint com mode: "test" e um payload de exemplo, e devolve o resultado cru — ninguém descobre integração quebrada na primeira fatura. O contrato da chamada está no guia.
Resposta 200:
| Campo | Tipo | Descrição |
|---|---|---|
outcome |
enum | Resultado da chamada — ver valores |
amountCents |
integer | null | Valor devolvido pelo seu endpoint, quando ok |
description |
string | null | Descrição devolvida, se houver |
responseStatus |
integer | null | HTTP status da resposta — null se nem respondeu |
latencyMs |
integer | Latência da chamada |
responseBody |
string | null | Corpo cru da resposta — é com isso que seu time debuga |
O enum outcome
Seção intitulada “O enum outcome”O mesmo enum aparece no histórico pricingAttempts das faturas:
| Valor | Significado |
|---|---|
ok |
Resposta válida — o valor seria aceito |
skip |
Seu endpoint respondeu { "skip": true } — o item não seria cobrado neste ciclo |
timeout |
Sem resposta em 10 segundos |
http_error |
Resposta com status fora de 2xx |
invalid_response |
2xx, mas corpo fora do contrato (ex.: amount float ou ausente) |
over_cap |
Valor acima do capCents do produto — tratado como falha por proteção |
Erros: 404 (produto não existe ou não é de preço variável).