Gerenciar promoções

Listar promoções

GET /api/promotions

Permissão: Cadastro de promoções — visualização

Retorna as promoções da loja (exceto as excluídas), com filtros e paginação.

Query params

ParâmetroTipoDescrição
pageintPágina, começando em 1. Padrão: 1.
limitintItens por página, de 1 a 100. Padrão: 10.
searchstringTrecho do nome, ID exato ou código de cupom. %, _ e [ são buscados como texto.
statusstringAtiva, Inativa, Expirada ou Programada, sem diferenciar maiúsculas (veja Status). Omita para trazer todas.
activeboolFiltra pelo indicador de ativação.
initialDatedatetimeInício do período a filtrar. Só tem efeito junto com finalDate: retorna as promoções cuja vigência se sobrepõe ao período.
finalDatedatetimeFim do período a filtrar. Só tem efeito junto com initialDate.
promotionsDateenumPeríodo pré-definido; quando informado, substitui initialDate/finalDate. Valores em Períodos (nome ou número).
hasCouponsboolSomente promoções com (true) ou sem (false) cupom.
hasMultipleCouponsboolSomente promoções com múltiplos cupons.
canAccumulateboolFiltra pelo indicador de acúmulo.
conditionslong[]Promoções que usam estes tipos de condição. IDs de GET /api/promotions/conditions (promotionConditionId).
actionslong[]Promoções que usam estes tipos de ação. IDs de GET /api/promotions/actions (promotionActionId).
orderFieldstringname, initialDate, finalDate ou order (prioridade), exatamente com essa grafia. Padrão: order.
orderBystringasc ou desc. Padrão: desc.

Valores fora das faixas e listas acima retornam 400 (VALIDATION_FAILED), com o parâmetro em details[].field.

Exemplo

curl "https://{loja}.commerce-wake.tech/api/promotions?status=Ativa&hasCoupons=true&page=1&limit=20&orderField=initialDate&orderBy=desc" \
  -H "Authorization: Bearer {access_token}" \
  -H "x-api-version: 2.0"

Resposta 200

{
  "data": [
    {
      "promotionId": 1234,
      "name": "Black Friday — 10% em Camisetas",
      "description": null,
      "initialDate": "2026-11-27T00:00:00-03:00",
      "finalDate": "2026-11-30T23:59:59-03:00",
      "active": true,
      "status": "Programada",
      "order": 1,
      "coupons": ["BLACK10"],
      "hasCoupon": true,
      "hasMultipleCoupons": false,
      "totalCouponsLimit": 1000,
      "totalCouponsPerCustomer": 1,
      "totalCouponsUsed": 0,
      "canAccumulate": true,
      "accumulationScope": 0,
      "accumulationLinkCount": 0
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 20
}
CampoDescrição
totalTotal de promoções que atendem aos filtros (para paginação).
page / limitPágina e tamanho de página usados.
orderPrioridade. Pode ser null.
descriptionSempre null na listagem. Use Obter promoção.
totalCouponsLimit / totalCouponsPerCustomerLimites de uso total e por cliente (0 = ilimitado).
totalCouponsUsedUsos já realizados (pedidos cancelados não contam).
accumulationLinkCountQuantidade de promoções na lista de acúmulo.

Períodos (promotionsDate)

ValorNomePeríodoValorNomePeríodo
1TodayHoje8Next30DaysPróximos 30 dias
2YesterdayOntem9Next6MonthsPróximos 6 meses
3Last7DaysÚltimos 7 dias10CurrentWeekSemana atual
4Last30DaysÚltimos 30 dias11CurrentMonthMês atual
5Last6MonthsÚltimos 6 meses12CurrentSemesterSemestre atual
6TomorrowAmanhã13Last12MonthsÚltimos 12 meses
7Next7DaysPróximos 7 dias

Obter promoção

GET /api/promotions/{promotionId}

Permissão: Cadastro de promoções — visualização

Retorna todos os dados de uma promoção. Retorna 404 se a promoção não existir ou tiver sido excluída.

Path paramTipoDescrição
promotionId (obrigatório)intID da promoção (de Listar promoções ou da resposta de criação).

Resposta 200

{
  "data": {
    "generalInformation": {
      "name": "Black Friday — 10% em Camisetas",
      "description": "Campanha de Black Friday",
      "isActive": true,
      "startDate": "2026-11-27T00:00:00-03:00",
      "endDate": "2026-11-30T23:59:59-03:00",
      "canAccumulate": true,
      "accumulationScope": 0,
      "order": 1,
      "totalUsageLimit": 1000,
      "usageLimitPerCustomer": 1
    },
    "couponInformation": {
      "couponCodes": ["BLACK10"],
      "multipleCouponConfig": { "hasMultipleCoupons": false, "allowCustomerToReuseSameCoupon": false },
      "couponTotalUsageLimit": 1000,
      "couponUsageLimitPerCustomer": 1
    },
    "conditionGroups": [
      {
        "id": 881,
        "name": "Grupo 1",
        "conditions": [
          {
            "conditionGroup": 5,
            "conditionSubGroup": 16,
            "conditionPromotionId": 5521,
            "logicOperator": { "id": 3, "arguments": [ { "id": 4, "name": "Valor", "values": ["199.90"], "optional": null } ] }
          }
        ]
      }
    ],
    "discounts": [
      {
        "discountGroup": 1,
        "discountSubGroup": 2,
        "discountPromotionId": 7310,
        "arguments": [
          { "id": 1,  "name": "ValorDesconto", "values": ["10"], "optional": false },
          { "id": 3,  "name": "CategoriasId", "values": ["15", "22"], "optional": false },
          { "id": 13, "name": "IgnorarProdutosRemarcados", "values": ["false"], "optional": false }
        ]
      }
    ],
    "entityRules": [ { "entityId": 3, "validationId": 4, "restrictionId": 2, "arguments": ["12"] } ],
    "stamps": [ { "stampId": 31, "name": "Black Friday", "active": true, "order": 1, "urlImage": "https://..." } ],
    "disclosure": [ { "type": "1", "title": "Black Friday", "content": "<strong>10% OFF</strong> em camisetas" } ],
    "partners": [],
    "accumulationLinks": [],
    "accumulationLinkDetails": [],
    "accumulationInboundLinks": []
  }
}

Campos que só existem na resposta

CampoDescrição
conditionGroups[].idID do grupo de condições. Pode ser reenviado no PUT, mas é ignorado.
conditionPromotionIdID da condição nesta promoção. Usado em Clientes de uma condição e Categorias de uma condição.
discountPromotionIdID do desconto nesta promoção. Usado em Categorias de um desconto.
arguments[].name / optionalNome do argumento e se ele é complementar.
stamps[].name / active / urlImageDados do selo.
accumulationLinkDetailsDetalhes (promotionId, name, isActive, startDate, endDate) das promoções em accumulationLinks.
accumulationInboundLinksPromoções que citam esta no acúmulo delas (promotionId, name, accumulationScope, isActive). Enquanto houver itens, esta promoção não pode passar a “não acumular”.

Os nomes de campos da resposta são os mesmos do corpo de Criar promoção. disclosure[].type volta como string ("1") e pode ser reenviado assim.

🚧

Os IDs internos mudam a cada atualização

conditionGroups[].id, conditionPromotionId e discountPromotionId são gerados de novo em todo PUT. Leia a promoção novamente antes de usá-los.

🚧

Exceção: condição de clientes específicos

A condição de clientes específicos retorna em values[0] a quantidade de clientes vinculados, não os IDs. Se a promoção tiver mais de uma condição desse tipo, só a primeira vem como quantidade; as demais vêm com a lista de IDs, que também não pode ser reenviada como está. Para reenviar uma promoção lida, siga Editar uma promoção existente.

Criar promoção

POST /api/promotions

Permissão: Cadastro de promoções — visualização e edição

Cria uma promoção. O mesmo corpo é usado em Atualizar promoção.

Corpo da requisição

CampoTipoDescrição
generalInformation (obrigatório)objectInformações gerais (tabela abaixo).
couponInformation (obrigatório)objectCupons. Envie mesmo em promoções sem cupom, com couponCodes: [].
conditionGroupsarrayGrupos de condições (veja Condições e grupos e Receitas). Omitido ou [] = sem condições.
discountsarrayDescontos (veja Receitas de descontos).
entityRulesarrayRegras adicionais (veja Regras adicionais).
stampsarray[{ "stampId": int, "order": int }]. IDs em Onde obter os IDs.
disclosurearray[{ "type": 1|2, "title": string, "content": string }]. 1 = spot, 2 = página de produto. title até 50 caracteres.
partnersint[]IDs de parceiros. [] = sem restrição.
accumulationLinkslong[]IDs de promoções da lista de acúmulo (veja Acúmulo).
🚧

Omita listas vazias ou envie [], nunca null

Listas omitidas valem como []. Na criação, discounts, entityRules, stamps, disclosure ou partners enviados como null resultam em erro 500. Em criação e atualização, values: null e conditions: null dentro de um grupo também resultam em 500. Uma condição ou desconto sem arguments (ou com null) é descartado sem erro. A exceção é accumulationLinks (veja Acúmulo).

generalInformation

CampoTipoDescrição
name (obrigatório)stringNome da promoção. Até 250 caracteres.
descriptionstringDescrição interna. Até 1000 caracteres.
startDate (obrigatório)datetimeInício da vigência.
endDate (obrigatório)datetimeFim da vigência. Maior que startDate e que o momento atual.
isActiveboolSe a promoção nasce ativa. Padrão: false.
orderintPrioridade (menor = avaliada antes). null, zero ou positivo. Padrão: null.
canAccumulateboolSe acumula com outras promoções. Padrão: false.
accumulationScopeint0 todas, 1 todas exceto as selecionadas, 2 somente as selecionadas. Padrão: 0.
totalUsageLimitintLimite total de usos. 0, null ou omitido = ilimitado.
usageLimitPerCustomerintLimite de usos por cliente. 0, null ou omitido = ilimitado.

Textos acima do limite retornam 400 com o campo em details[].field.

couponInformation

CampoTipoDescrição
couponCodes (obrigatório)string[]Códigos de cupom. [] para promoções sem cupom. A quantidade define o tipo da promoção (veja Cupons).
multipleCouponConfig.allowCustomerToReuseSameCouponboolPermite que o mesmo cliente reutilize um código. Só com mais de um cupom.
multipleCouponConfig.hasMultipleCouponsboolSomente leitura. É deduzido da quantidade de códigos e ignorado na escrita.
couponTotalUsageLimitintAlternativa a totalUsageLimit (usada quando este é null).
couponUsageLimitPerCustomerintAlternativa a usageLimitPerCustomer.

Condição, desconto e regra adicional

ObjetoFormato
Grupo de condições{ "name": string, "conditions": [condição] }
Condição{ "conditionGroup": int, "conditionSubGroup": int, "logicOperator": { "id": int, "arguments": [{ "id": int, "values": [string] }] } }
Desconto{ "discountGroup": int, "discountSubGroup": int, "arguments": [{ "id": int, "values": [string] }] }
Regra adicional{ "entityId": int, "validationId": int, "restrictionId": int, "arguments": [string] }

arguments e values nunca devem ser null; use []. Argumentos com values vazio são descartados. Campos extras (como name dos argumentos, presentes na resposta do GET) são ignorados.

Exemplo

Cupom BLACK10: com subtotal a partir de R$ 199,90 e entrega em SP ou RJ, dá 10% nas categorias 15 e 22 e frete grátis, sem descontar produtos da marca 12.

curl -X POST "https://{loja}.commerce-wake.tech/api/promotions" \
  -H "Authorization: Bearer {access_token}" \
  -H "x-api-version: 2.0" \
  -H "Content-Type: application/json" \
  -d '{
  "generalInformation": {
    "name": "Black Friday — 10% em Camisetas",
    "description": "Campanha de Black Friday",
    "startDate": "2026-11-27T00:00:00-03:00",
    "endDate": "2026-11-30T23:59:59-03:00",
    "isActive": true,
    "order": 1,
    "canAccumulate": true,
    "accumulationScope": 0,
    "totalUsageLimit": 1000,
    "usageLimitPerCustomer": 1
  },
  "couponInformation": {
    "couponCodes": ["BLACK10"],
    "multipleCouponConfig": { "allowCustomerToReuseSameCoupon": false }
  },
  "conditionGroups": [
    {
      "name": "Grupo 1",
      "conditions": [
        { "conditionGroup": 5, "conditionSubGroup": 16,
          "logicOperator": { "id": 3, "arguments": [ { "id": 4, "values": ["199.90"] } ] } },
        { "conditionGroup": 2, "conditionSubGroup": 7,
          "logicOperator": { "id": 7, "arguments": [ { "id": 9, "values": ["25", "19"] } ] } }
      ]
    }
  ],
  "discounts": [
    { "discountGroup": 1, "discountSubGroup": 2,
      "arguments": [
        { "id": 1,  "values": ["10"] },
        { "id": 3,  "values": ["15", "22"] },
        { "id": 13, "values": ["false"] }
      ] },
    { "discountGroup": 2, "discountSubGroup": 2,
      "arguments": [ { "id": 1, "values": ["100"] } ] }
  ],
  "entityRules": [
    { "entityId": 3, "validationId": 4, "restrictionId": 2, "arguments": ["12"] }
  ],
  "stamps": [ { "stampId": 31, "order": 1 } ],
  "disclosure": [ { "type": 1, "title": "Black Friday", "content": "<strong>10% OFF</strong> em camisetas" } ],
  "partners": []
}'

No exemplo: 5/16 com operador 3 e argumento 4 = subtotal ≥ R$ 199,90; 2/7 com operador 7 e argumento 9 = estado de entrega em São Paulo (25) ou Rio de Janeiro (19); o desconto 1/2 com argumentos 1 e 3 = 10% nas categorias; 2/2 com 100 = 100% de desconto no frete; a regra adicional 3/4/2 = não aplicar o desconto em produtos da marca 12.

Resposta 201

Header Location: /api/promotions/1234.

{ "data": { "promotionId": 1234 } }

Resposta 400

{
  "code": "VALIDATION_FAILED",
  "displayMessage": "A promoção não pôde ser salva",
  "details": [
    { "code": "END_DATE_IS_REQUIRED", "issue": "A data de término é um campo obrigatório" },
    { "code": "DISCOUNT_PERCENTAGE_OUT_OF_RANGE", "issue": "O percentual de desconto da ação (Id 5) deve ser maior que 0 e no máximo 100; valor informado: 150" }
  ],
  "traceId": "0HN7…:00000001"
}

Os códigos estão em Códigos de erro. A validação acontece em duas etapas: primeiro as regras acima; só se todas passarem, as regras da gravação (datas, cupons já usados, IDs inexistentes, operadores e argumentos incompatíveis) são conferidas e voltam como um item PRIVATE_API_VALIDATION, com uma ou mais mensagens separadas por quebra de linha em issue. Corrija tudo e reenvie, porque uma nova etapa pode trazer outros erros.

Atualizar promoção

PUT /api/promotions/{promotionId}

Permissão: Cadastro de promoções — visualização e edição

Substitui todos os dados da promoção pelo conteúdo enviado. O corpo é idêntico ao de Criar promoção.

❗

Substituição completa

Condições, descontos, regras adicionais, selos, textos de divulgação e parceiros que não estiverem no corpo são removidos — uma lista omitida vale como []. Para alterar um único campo, leia a promoção, altere o campo e reenvie tudo — veja Editar uma promoção existente.

A única exceção é accumulationLinks com escopo 1 ou 2: se for omitido (ou null), os vínculos atuais são mantidos (veja Acúmulo).

Path paramTipoDescrição
promotionId (obrigatório)intID da promoção.

Grupos de condições, condições e descontos são recriados a cada atualização, então conditionGroups[].id, conditionPromotionId e discountPromotionId mudam. Leia a promoção de novo antes de usar esses IDs.

Resposta 200

{ "data": { "promotionId": 1234 } }

Erros

StatusQuando
400Corpo inválido ou regra de negócio recusada, no mesmo formato de Criar promoção. Os erros de formato e da primeira etapa de regras podem aparecer antes do 404.
404Promoção inexistente ou excluída. code NOT_FOUND; o texto “Promoção não encontrada.” vem em details[0].issue.
500Erro inesperado ao gravar. Leia a promoção para conferir o estado e tente de novo.
🚧

Promoções gravadas com percentual fora da faixa

Promoções antigas com desconto percentual igual a 0 ou acima de 100 recebem 400 (DISCOUNT_PERCENTAGE_OUT_OF_RANGE) em qualquer atualização até o valor ser corrigido para a faixa (0, 100].

Ativar / inativar promoção

PUT /api/promotions/{promotionId}/status

Permissão: Ativação de promoções — visualização e edição

Liga ou desliga a promoção sem alterar o restante do cadastro.

CampoTipoDescrição
active (obrigatório)booltrue ativa, false inativa.
startDatedatetimeNova data de início. Use só ao ativar, junto com endDate.
endDatedatetimeNova data de término, maior que o início e que o momento atual.
📘

Vigência ao ativar

Sem datas, a promoção é ativada com a vigência atual. Se essa vigência já terminou, a resposta é 400, com displayMessage contendo “A promoção está expirada; informe uma nova data de término para ativá-la.” (a mensagem pode trazer vários motivos, um por linha): envie startDate e endDate, que então substituem a vigência. Enviar só uma das datas é recusado. Ao inativar, omita as datas: elas não são gravadas.

curl -X PUT "https://{loja}.commerce-wake.tech/api/promotions/1234/status" \
  -H "Authorization: Bearer {access_token}" -H "x-api-version: 2.0" -H "Content-Type: application/json" \
  -d '{ "active": true }'

Resposta 200

{ "data": { "success": true, "message": "..." } }

Erros

StatusQuando
400active ausente, datas incompletas ou inválidas, promoção expirada sem nova data. O motivo vem em displayMessage.
404Promoção inexistente ou excluída.
500Erro inesperado.

Duplicar promoção

POST /api/promotions/duplicate

Permissão: Cadastro de promoções — visualização e edição

Cria uma cópia de uma promoção existente com um novo nome.

CampoTipoDescrição
promotionId (obrigatório)longID da promoção de origem.
name (obrigatório)stringNome da nova promoção. Não pode repetir o nome de outra promoção.
{ "promotionId": 1234, "name": "Black Friday — 10% em Camisetas (cópia)" }

Resposta 201

Header Location: /api/promotions/1250.

{ "data": { "promotionId": 1250 }, "message": "... Nova promoção com Id: 1250" }

Erros

StatusQuando
400name ausente.
404A promoção de origem não existe ou promotionId não foi informado (verificado antes do nome).
409O nome já é usado por outra promoção (“Já existe uma promoção com o nome preenchido”).
500Erro inesperado.

Revise a cópia com Obter promoção antes de ativá-la: se a vigência da origem já passou, ajuste as datas com Atualizar promoção.

Excluir promoções

POST /api/promotions/batch-delete

Permissão: Cadastro de promoções — visualização e edição

Exclui de 1 a 100 promoções por requisição. Promoções excluídas deixam de ser aplicadas e de aparecer nas consultas. IDs repetidos são processados uma vez.

CampoTipoDescrição
promotionIds (obrigatório)long[]De 1 a 100 IDs, todos maiores que zero. Fora disso a resposta é 400 (VALIDATION_FAILED).
curl -X POST "https://{loja}.commerce-wake.tech/api/promotions/batch-delete" \
  -H "Authorization: Bearer {access_token}" -H "x-api-version: 2.0" -H "Content-Type: application/json" \
  -d '{ "promotionIds": [1234, 1250] }'

Resposta 200

{
  "data": {
    "results": [
      { "promotionId": 1234, "deleted": true,  "message": "Promoção deletada com sucesso." },
      { "promotionId": 1250, "deleted": false, "message": "Promoção não encontrada." }
    ]
  }
}

O resultado é individual: verifique deleted de cada item. A resposta é 200 sempre que houver resultado por item, mesmo quando nenhuma promoção é excluída. Uma falha da requisição inteira (sem resultado por item) retorna um status de erro, normalmente 500.

Histórico de alterações

GET /api/promotions/{promotionId}/logs

Permissão: Cadastro de promoções — visualização e edição

Retorna as alterações mais recentes registradas para a promoção nos últimos 120 dias, da mais nova para a mais antiga. Cada registro traz quem alterou, quando, e uma fotografia da promoção naquele momento. O único parâmetro de consulta suportado é limit; não envie outros.

ParâmetroTipoDescrição
promotionId (obrigatório)int (path)ID da promoção.
limitint (query)Quantidade de registros, de 1 a 100. Padrão: 10.
{
  "data": [
    { "changeDate": "2026-10-14T16:32:10-03:00", "user": "integracao-erp", "payload": "{\"dataAlteracao\":\"...\",\"autor\":\"integracao-erp\", ...}" }
  ],
  "total": 1, "totalGeneral": 1, "page": 1, "limit": 10
}
CampoDescrição
changeDateData e hora da alteração.
userAutor da alteração, obtido do token (o client_id, para aplicações).
payloadString com o JSON completo do registro de histórico.
totalQuantidade de registros retornados nesta resposta (não o total existente).