Upsert por Lote
Crie ou atualize até 1000 estados de ciclo de vida em uma única operação, com relatório de resultado por item.
POST /api/commerce-lifecycle-snapshots/batch-upsert
Cria ou atualiza (upsert) até 1000 estados de ciclo de vida em uma única operação. É a forma recomendada para o motor sincronizar um tenant inteiro de forma eficiente.
Auth: Obrigatório — permissão commerce-lifecycle-snapshot:create
Sucesso parcial. A request nunca falha por completo por causa de items
inválidos: cada item é validado de forma independente, os válidos são
persistidos, e os que falham são reportados em errors[] com um código
estável. Sempre verifique summary.failed e errors na resposta.
Identificador dual homogêneo
Cada item identifica seu comércio com exatamente um de commerce_id (UUID) ou external_id (código externo). Além disso, todos os items do lote devem usar o mesmo tipo de identificador — misturar commerce_id e external_id no mesmo lote responde 400.
Corpo da Requisição
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
items | object[] | Sim | Lista de estados a gravar (mínimo 1, máximo 1000). |
Cada elemento de items:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
commerce_id | UUID | Um dos dois | UUID do comércio. Excludente com external_id. |
external_id | string | Um dos dois | Código externo do comércio (1–255 caracteres). Excludente com commerce_id. |
activity_generation_config_id | UUID | Sim | AGC sob a qual o estado é gravado. |
code | string | Sim | Código do estado de retenção (1–255 caracteres). Deve corresponder a um código válido do tenant (estados de usuário). |
stage | enum | Sim | Estágio: ACTIVATION, ENGAGEMENT ou RESURRECTION. |
last_computed_at | ISO 8601 | Sim | Quando o motor calculou este estado. |
Limite de taxa (rate limit)
Este endpoint é limitado a 300 requests por minuto por IP. Ao exceder, responde 429 Too Many Requests com um header Retry-After; respeite-o para regular o ritmo de envio. Com lotes cheios de 1000 items, isso permite sincronizar até ~300.000 estados por minuto.
Exemplo
curl -X POST "https://api.reten.ai/api/commerce-lifecycle-snapshots/batch-upsert" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"external_id": "SV-001",
"activity_generation_config_id": "a1b2c3d4-0000-4000-8000-000000000001",
"code": "AT_RISK_MONTHLY",
"stage": "ENGAGEMENT",
"last_computed_at": "2026-06-10T06:00:00.000Z"
},
{
"external_id": "SV-004",
"activity_generation_config_id": "a1b2c3d4-0000-4000-8000-000000000001",
"code": "ONBOARDING",
"stage": "ACTIVATION",
"last_computed_at": "2026-06-10T06:00:00.000Z"
},
{
"external_id": "SV-003",
"activity_generation_config_id": "a1b2c3d4-0000-4000-8000-000000000001",
"code": "WIN_BACK",
"stage": "RESURRECTION",
"last_computed_at": "2026-06-10T06:00:00.000Z"
},
{
"external_id": "SV-099",
"activity_generation_config_id": "a1b2c3d4-0000-4000-8000-000000000001",
"code": "HEALTHY",
"stage": "ENGAGEMENT",
"last_computed_at": "2026-06-10T06:00:00.000Z"
}
]
}'const response = await fetch(
"https://api.reten.ai/api/commerce-lifecycle-snapshots/batch-upsert",
{
method: "POST",
headers: {
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
items: [
{ external_id: "SV-001", activity_generation_config_id: "a1b2c3d4-0000-4000-8000-000000000001", code: "AT_RISK_MONTHLY", stage: "ENGAGEMENT", last_computed_at: "2026-06-10T06:00:00.000Z" },
{ external_id: "SV-004", activity_generation_config_id: "a1b2c3d4-0000-4000-8000-000000000001", code: "ONBOARDING", stage: "ACTIVATION", last_computed_at: "2026-06-10T06:00:00.000Z" },
{ external_id: "SV-003", activity_generation_config_id: "a1b2c3d4-0000-4000-8000-000000000001", code: "WIN_BACK", stage: "RESURRECTION", last_computed_at: "2026-06-10T06:00:00.000Z" },
{ external_id: "SV-099", activity_generation_config_id: "a1b2c3d4-0000-4000-8000-000000000001", code: "HEALTHY", stage: "ENGAGEMENT", last_computed_at: "2026-06-10T06:00:00.000Z" },
],
}),
},
);
const result = await response.json();import requests
response = requests.post(
"https://api.reten.ai/api/commerce-lifecycle-snapshots/batch-upsert",
headers={"x-api-key": "YOUR_API_KEY"},
json={
"items": [
{"external_id": "SV-001", "activity_generation_config_id": "a1b2c3d4-0000-4000-8000-000000000001", "code": "AT_RISK_MONTHLY", "stage": "ENGAGEMENT", "last_computed_at": "2026-06-10T06:00:00.000Z"},
{"external_id": "SV-004", "activity_generation_config_id": "a1b2c3d4-0000-4000-8000-000000000001", "code": "ONBOARDING", "stage": "ACTIVATION", "last_computed_at": "2026-06-10T06:00:00.000Z"},
{"external_id": "SV-003", "activity_generation_config_id": "a1b2c3d4-0000-4000-8000-000000000001", "code": "WIN_BACK", "stage": "RESURRECTION", "last_computed_at": "2026-06-10T06:00:00.000Z"},
{"external_id": "SV-099", "activity_generation_config_id": "a1b2c3d4-0000-4000-8000-000000000001", "code": "HEALTHY", "stage": "ENGAGEMENT", "last_computed_at": "2026-06-10T06:00:00.000Z"},
],
},
)
result = response.json()Resposta 200 OK
Neste exemplo, três items foram gravados com sucesso e um falhou (SV-099 não existe no tenant):
{
"summary": {
"total": 4,
"created": 1,
"updated": 2,
"failed": 1
},
"errors": [
{
"index": 3,
"identifier": "SV-099",
"error": "unresolved_commerce",
"message": "No commerce exists with external_id \"SV-099\"."
}
]
}Campos da resposta
| Campo | Tipo | Descrição |
|---|---|---|
summary.total | number | Total de items enviados. |
summary.created | number | Items inseridos (estados novos). |
summary.updated | number | Items atualizados (estados existentes modificados). |
summary.failed | number | Items que falharam na validação e não foram persistidos. |
errors | object[] | Detalhe por item que falhou (vazio se todos tiveram sucesso). |
errors[].index | number | Posição (base 0) do item dentro do array items enviado. |
errors[].identifier | string | O identificador (commerce_id ou external_id) do item que falhou. |
errors[].error | string | Código de erro estável (veja tabela abaixo). |
errors[].message | string | Mensagem legível para humanos/logs. |
Códigos de erro por item
error | Significado |
|---|---|
unresolved_commerce | O comércio (por commerce_id ou external_id) não existe no tenant. |
unknown_code | O code não corresponde a nenhum UserRetentionStatusConfig do tenant. |
duplicate_in_batch | O par (commerce, AGC) aparece mais de uma vez no mesmo lote. Remova duplicatas antes de enviar. |
Erros (da request completa)
| Status | Descrição |
|---|---|
400 | items vazio ou com mais de 1000 elementos; um item com ambos ou nenhum identificador; ou uma mistura de commerce_id e external_id no lote. |
429 | O limite de 300 requests/minuto foi excedido. Respeite o header Retry-After. |
401 | Chave de API ausente ou inválida. |
403 | A credencial não tem a permissão commerce-lifecycle-snapshot:create. |