Promoções e cupons
Como as promoções funcionam, como se combinam com cupons e como criá-las pela API do administrador.
Uma promoção é uma regra de desconto que a loja cadastra uma vez e a plataforma aplica sozinha no carrinho ou no preço da vitrine. Quando a promoção tem um código ela funciona como cupom: o cliente precisa digitar o código. Sem código ela é automática. Estas são as rotas do administrador da loja (/api/v1/admin/...); a vitrine e o checkout leem as mesmas promoções para calcular o que o cliente paga.
Como funciona
Ciclo de vida
status | Significado | Aplica no carrinho? |
|---|---|---|
A | Ativa. Vale entre valid_from e valid_until. | Sim, dentro da vigência e do limite de uso |
D | Inativa (também usada como rascunho). | Não |
E | Expirada. | Não |
Uma promoção ativa só desconta quando a vigência está aberta (valid_from já passou e valid_until ainda não), o limite de usos (usage_limit, per_user_limit) não foi atingido, as condições batem com o carrinho e nenhum orçamento associado (veja Orçamentos e grupos) está esgotado.
Fluxo típico de integração
- Escolha a zona e o tipoDecida se o desconto aparece no preço do produto (
catalog) ou só quando o cliente monta o carrinho (cart), e qualtypeusar. - Crie a promoção
POST /api/v1/admin/promotionscom nome, tipo, desconto e, se for cupom, ocode. Veja Criar promoção. - Confira o cupom
POST /api/v1/admin/coupons/validatediz se o código seria aceito e qual desconto daria. Veja Cupons. - Acompanhe o resultado
GET /api/v1/admin/promotions/statsmostra usos, clientes únicos e desconto concedido. Veja Estatísticas e projeção. - Encerre
Conceitos importantes
Tipos de promoção (type)
Os tipos do editor do painel são quatro. Os demais existem para o modelo de bônus e para promoções criadas por integração.
type | O que faz | discount |
|---|---|---|
percentage | Percentual de desconto. | Pontos-base, de 1 a 10000 |
fixed | Valor fixo de desconto. | Centavos |
free_shipping | Frete grátis (opcionalmente só para uma forma de envio, em free_shipping_method_id). | Pode ser 0 |
buy_x_get_y | Compre X leve Y. Exige conditions.buy_x_get_y com buy_quantity e get_quantity maiores que zero. | Pode ser 0 |
to_percentage, to_fixed | O preço final passa a ser o valor indicado, em vez de "tantos por cento a menos". | Pontos-base ou centavos |
order_discount, product_discount, category_discount, free_products, give_usergroup, give_coupon, free_product, assign_usergroup | Tipos herdados do modelo de bônus. Em integrações novas prefira percentage, fixed e a lista bonuses. | Conforme o tipo |
Um valor fora da lista leva 400 e a mensagem traz todos os tipos aceitos. Um desconto negativo, ou um percentage acima de 10000, também é recusado.
Zona: catálogo ou carrinho
catalog: o desconto entra no preço do produto na vitrine, na busca e na mini-loja. Só enxerga condições do produto (produto, categoria, seller, preço, quantidade, usuário). Condições de carrinho (subtotal, frete, forma de pagamento, cupom) são recusadas com400.cart: o desconto é calculado quando o cliente monta o carrinho e no checkout. Aceita todas as condições.
Se você não enviar zone, a plataforma escolhe cart para buy_x_get_y, para tipos que o catálogo não calcula e para qualquer promoção com código; as demais ficam em catalog. Uma zona escrita à mão que não calcula o tipo (por exemplo buy_x_get_y em catalog) é recusada com 400.
Condições
conditions é um objeto simples, todas as chaves opcionais:
| Chave | Tipo | Significado |
|---|---|---|
min_cart_total | inteiro, centavos | Subtotal mínimo do carrinho. |
product_ids | lista de UUID | Pelo menos um item do carrinho precisa ser um destes produtos. |
category_ids | lista de UUID | Pelo menos um item precisa ser de uma destas categorias. |
exclude_product_ids | lista de UUID | Nenhum item pode ser destes produtos. |
user_group_ids | lista de UUID | Só vale para clientes destes grupos de usuários. |
vendor_ids | lista de UUID | Só vale para itens destes sellers. |
buy_x_get_y | objeto | Mecânica do brinde: buy_product_ids, buy_quantity, get_product_ids, get_quantity, discount_percent (1 a 100; 100 = de graça). |
Para regras com E/OU use condition_groups: um objeto {"set":"all"|"any","set_value":true,"conditions":[...]} cujas folhas são {"type":"subtotal","operator":"gte","value":20000} e que podem ter grupos dentro de grupos. Operadores: eq, neq, lt, gt, lte, gte, in, nin, cont, ncont. Tipos de folha válidos em qualquer zona: price, product_quantity, categories, products, vendor, users, feature. Só na zona cart: subtotal, products_number, total_weight, country, state, zip_postal_code, customer_state, customer_city, customer_cep_range, shipping_zone, customer_region, payment_method, shipping_method, coupon_code, multiple_coupon_codes, once_per_customer, number_of_usages, purchased_products, usergroup, product_qty, product_min_qty, category_qty, category_min_qty, first_order, first_order_only, once_per_cnpj, days_since_last_order, no_orders_in_days, categories_purchased_in_period, categories_prev_month, avg_ticket_p6m, loyalty_tier. Um tipo desconhecido é recusado, nunca ignorado.
Cupom: um código por promoção
O campo code guarda um código por promoção. A plataforma o grava em maiúsculas, sem caracteres invisíveis (por exemplo o espaço de largura zero que o WhatsApp cola junto) e a busca na hora de usar ignora maiúsculas e minúsculas. Dois códigos iguais não podem existir em promoções não excluídas: o segundo leva 409 coupon_code_in_use. Para ter muitos códigos descartáveis a partir de uma promoção-modelo use generate-auto-coupons (veja Cupons).
Quem é o dono: loja toda ou seller
vendor_id vazio significa promoção da loja/marketplace: entra no cálculo automático. Com vendor_id preenchido a promoção é de um seller, o desconto incide só sobre os itens dele e ela só funciona por código de cupom, nunca de forma automática.
Proteções que a API aplica por você
- Loja toda por engano. Uma promoção de catálogo sem nenhum produto nem categoria vale para a loja inteira. Para gravá-la você precisa confirmar com
?confirm_store_wide=true; sem isso a resposta é400 store_wide_unconfirmed. A mesma pergunta aparece quando uma edição muda o alcance e o resultado é a loja toda. - Piso de margem. Se a loja usa o Protetor de Margem, uma oferta que leva algum produto abaixo da margem mínima é recusada com
422e o detalhamento da conta. Um token de exceção aprovado vai no cabeçalhoX-Margin-Override. - Campo desconhecido não passa calado. Chaves do corpo que a rota não conhece são devolvidas em
ignored_fieldsewarning(e no cabeçalhoX-Campos-Ignorados). UmPUTque não muda nada leva400em vez de200.
Endpoints
| Método e caminho | Para que serve | Auth | Página |
|---|---|---|---|
GET /api/v1/admin/promotions | Lista as promoções com filtros | admin | Listar e detalhar |
GET /api/v1/admin/promotions/{id} | Detalha uma promoção | admin | Listar e detalhar |
POST /api/v1/admin/promotions | Cria uma promoção | admin | Criar promoção |
PUT /api/v1/admin/promotions/{id} | Altera uma promoção | admin | Alterar e excluir |
DELETE /api/v1/admin/promotions/{id} | Exclui (lógica) uma promoção | admin | Alterar e excluir |
POST /api/v1/admin/promotions/bulk | Ativa, desativa ou exclui várias | admin | Ações em lote |
GET /api/v1/admin/promotions/{id}/coupons | Mostra o cupom da promoção | admin | Cupons |
POST /api/v1/admin/promotions/{id}/coupons | Define o código da promoção | admin | Cupons |
POST /api/v1/admin/promotions/generate-auto-coupons | Gera códigos descartáveis | admin | Cupons |
POST /api/v1/admin/coupons/validate | Simula a validade de um código | admin | Cupons |
GET /api/v1/admin/promotions/stats | Estatísticas de uso | admin | Estatísticas e projeção |
GET /api/v1/admin/promotions/uplift | Projeção de retorno de um desconto | admin | Estatísticas e projeção |
GET /api/v1/admin/promotions/free-shipping-regional | Lista as regras de frete grátis por região | admin | Frete grátis regional |
PUT /api/v1/admin/promotions/free-shipping-regional | Substitui as regras de frete grátis por região | admin | Frete grátis regional |
GET /api/v1/admin/promotions/free-shipping-regional/suggestions | Sugere o valor mínimo por região | admin | Frete grátis regional |
Erros comuns
- Mandar
discount: 10achando que é 10%. O percentual é em pontos-base: 10% é1000.10seria 0,1%. - Datas sem hora.
valid_fromevalid_untilsão RFC 3339 completos (2026-12-31T23:59:59-03:00).2026-12-31leva400com o formato aceito na mensagem. - Usar nomes de outro sistema.
title,start_at,end_at,from_dateeexpiry_datenão existem na entrada. A resposta devolvefrom_date/expiry_date, mas quem lê a resposta e devolve esses nomes não define a vigência: usevalid_fromevalid_until. Semvalid_untila promoção vale para sempre. - Promoção de catálogo sem alvo. Sem produtos nem categorias ela desconta a loja inteira. A API pede
confirm_store_wide=truejustamente para isso. - Condição de carrinho na zona
catalog.subtotal,coupon_codee semelhantes só existem emcart. - Tentar um segundo cupom na mesma promoção.
POST /promotions/{id}/couponsresponde409se já existe código; troque comPUTou gere códigos descartáveis. - Esperar que
nulllimpe um campo. NoPUT,nullou vazio significa "não mexe". A única exceção é"code": null, que remove o código.