Upsert por Lote
Crie ou atualize até 500 operadores em uma única operação, com relatório de resultado por item.
POST /api/integration/operators/batch-upsert
Cria ou atualiza (upsert) até 500 operadores em uma única operação. É o ponto de entrada de integração para carregar o quadro de operadores (vendedores, ativadores, agentes de call center) a partir de um sistema externo.
Auth: Obrigatório — permissão operator:batch-upsert
Sucesso parcial. Depois que o corpo passa na validação de formato, cada
item é processado de forma independente: os que podem ser persistidos são
persistidos, e os que falham são reportados em errors[] com um código
estável — um item que falha não desfaz os demais. Sempre verifique
summary.failed e errors na resposta.
Chave do upsert
Cada item é procurado entre os operadores existentes pelo par type + external_id. Se existir um operador com esse par, ele é atualizado (conta em summary.updated); caso contrário, é criado (summary.created). O mesmo external_id sob outro type é outro operador. Na atualização, metadata é mesclada com a existente: as chaves enviadas sobrescrevem as de mesmo nome, as não enviadas são mantidas, e enviar uma chave com null a remove. A mesclagem é recursiva (RFC 7386 JSON Merge Patch): os objetos aninhados são mesclados nível por nível, então você pode atualizar uma chave dentro de um objeto aninhado sem reenviar as irmãs. Qualquer outro valor substitui o anterior por completo, incluindo os arrays, que nunca são mesclados elemento a elemento. Enviar metadata: null esvazia o saco inteiro.
Corpo da Requisição
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
items | object[] | Sim | Lista de operadores a gravar (mínimo 1, máximo 500). |
Cada elemento de items:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
type | enum | Sim | Tipo de operador: SALESMAN, ACTIVATOR ou CALLCENTER. Parte da chave do upsert. |
external_id | string | Sim | Identificador do operador no sistema externo. Parte da chave do upsert. |
name | string | Sim | Nome do operador. |
email | string | Não | E-mail. Deve ser um e-mail válido e não pode estar em uso por outro operador. |
phone | string | Não | Número de telefone. |
metadata | object | Não | JSON arbitrário. Mesclado recursivamente com a metadata existente. |
Exemplo
curl -X POST "https://api.reten.ai/api/integration/operators/batch-upsert" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"type": "SALESMAN",
"external_id": "OP-001",
"name": "Juan Pérez",
"email": "juan.perez@example.com",
"phone": "+56912345678",
"metadata": { "zone": "north" }
},
{
"type": "ACTIVATOR",
"external_id": "OP-002",
"name": "Ana López"
},
{
"type": "CALLCENTER",
"external_id": "OP-003",
"name": "Carla Soto",
"email": "juan.perez@example.com"
}
]
}'const response = await fetch(
"https://api.reten.ai/api/integration/operators/batch-upsert",
{
method: "POST",
headers: {
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
items: [
{ type: "SALESMAN", external_id: "OP-001", name: "Juan Pérez", email: "juan.perez@example.com", phone: "+56912345678", metadata: { zone: "north" } },
{ type: "ACTIVATOR", external_id: "OP-002", name: "Ana López" },
{ type: "CALLCENTER", external_id: "OP-003", name: "Carla Soto", email: "juan.perez@example.com" },
],
}),
},
);
const result = await response.json();import requests
response = requests.post(
"https://api.reten.ai/api/integration/operators/batch-upsert",
headers={"x-api-key": "YOUR_API_KEY"},
json={
"items": [
{"type": "SALESMAN", "external_id": "OP-001", "name": "Juan Pérez", "email": "juan.perez@example.com", "phone": "+56912345678", "metadata": {"zone": "north"}},
{"type": "ACTIVATOR", "external_id": "OP-002", "name": "Ana López"},
{"type": "CALLCENTER", "external_id": "OP-003", "name": "Carla Soto", "email": "juan.perez@example.com"},
],
},
)
result = response.json()Resposta 200 OK
Neste exemplo, OP-001 já existia e foi atualizado, OP-002 foi criado, e OP-003 falhou porque seu email já está em uso por outro operador:
{
"summary": {
"total": 3,
"created": 1,
"updated": 1,
"failed": 1
},
"errors": [
{
"index": 2,
"identifier": "OP-003",
"error": "upsert_error",
"message": "Email juan.perez@example.com already exists in operator 9c4b2e7a-1d5f-4a8b-b3c6-7e8f9a0b1c2d"
}
]
}Campos da resposta
| Campo | Tipo | Descrição |
|---|---|---|
summary.total | number | Total de items enviados. |
summary.created | number | Items inseridos (operadores novos). |
summary.updated | number | Items atualizados (operadores existentes modificados). |
summary.failed | number | Items que não puderam ser 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 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 com a causa. |
Códigos de erro por item
error | Significado |
|---|---|
upsert_error | O item não pôde ser persistido; message traz a causa (p. ex. um email já em uso por outro operador). |
Erros (da request completa)
| Status | Descrição |
|---|---|
400 | items vazio ou com mais de 500 elementos; um item que não cumpre o formato (campo obrigatório ausente, tipo ou valor de enum inválido); ou uma propriedade não declarada aqui. |
401 | Chave de API ausente ou inválida. |
403 | A chave de API não tem a permissão operator:batch-upsert. |