Reten Docs
Estados de Ciclo de Vida de Comércios

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

CampoTipoObrigatórioDescrição
itemsobject[]SimLista de estados a gravar (mínimo 1, máximo 1000).

Cada elemento de items:

CampoTipoObrigatórioDescrição
commerce_idUUIDUm dos doisUUID do comércio. Excludente com external_id.
external_idstringUm dos doisCódigo externo do comércio (1–255 caracteres). Excludente com commerce_id.
activity_generation_config_idUUIDSimAGC sob a qual o estado é gravado.
codestringSimCódigo do estado de retenção (1–255 caracteres). Deve corresponder a um código válido do tenant (estados de usuário).
stageenumSimEstágio: ACTIVATION, ENGAGEMENT ou RESURRECTION.
last_computed_atISO 8601SimQuando 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

CampoTipoDescrição
summary.totalnumberTotal de items enviados.
summary.creatednumberItems inseridos (estados novos).
summary.updatednumberItems atualizados (estados existentes modificados).
summary.failednumberItems que falharam na validação e não foram persistidos.
errorsobject[]Detalhe por item que falhou (vazio se todos tiveram sucesso).
errors[].indexnumberPosição (base 0) do item dentro do array items enviado.
errors[].identifierstringO identificador (commerce_id ou external_id) do item que falhou.
errors[].errorstringCódigo de erro estável (veja tabela abaixo).
errors[].messagestringMensagem legível para humanos/logs.

Códigos de erro por item

errorSignificado
unresolved_commerceO comércio (por commerce_id ou external_id) não existe no tenant.
unknown_codeO code não corresponde a nenhum UserRetentionStatusConfig do tenant.
duplicate_in_batchO par (commerce, AGC) aparece mais de uma vez no mesmo lote. Remova duplicatas antes de enviar.

Erros (da request completa)

StatusDescrição
400items 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.
429O limite de 300 requests/minuto foi excedido. Respeite o header Retry-After.
401Chave de API ausente ou inválida.
403A credencial não tem a permissão commerce-lifecycle-snapshot:create.