Upsert por Lote
Crea o actualiza hasta 500 operadores en una sola operación, con reporte de resultado por item.
POST /api/integration/operators/batch-upsert
Crea o actualiza (upsert) hasta 500 operadores en una sola operación. Es el punto de entrada de integración para cargar la dotación de operadores (vendedores, activadores, agentes de call center) desde un sistema externo.
Auth: Requerida — permiso operator:batch-upsert
Éxito parcial. Una vez que el cuerpo pasa la validación de formato, cada
item se procesa de forma independiente: los que se pueden persistir se
persisten, y los que fallan se reportan en errors[] con un código estable —
un item que falla no deshace a los demás. Revisa siempre summary.failed y
errors en la respuesta.
Clave del upsert
Cada item se busca entre los operadores existentes por el par type + external_id. Si existe un operador con ese par se actualiza (cuenta en summary.updated); si no, se crea (summary.created). El mismo external_id bajo otro type es otro operador. En la actualización, metadata se fusiona con la existente: las claves enviadas sobrescriben las del mismo nombre, las no enviadas se conservan, y enviar una clave con null la elimina. La fusión es recursiva (RFC 7386 JSON Merge Patch): los objetos anidados se fusionan nivel por nivel, así podés actualizar una clave dentro de un objeto anidado sin reenviar sus hermanas. Cualquier otro valor reemplaza al anterior por completo, incluidos los arrays, que nunca se fusionan elemento a elemento. Enviar metadata: null vacía la bolsa entera.
Cuerpo de la Petición
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
items | object[] | Sí | Lista de operadores a escribir (mínimo 1, máximo 500). |
Cada elemento de items:
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
type | enum | Sí | Tipo de operador: SALESMAN, ACTIVATOR o CALLCENTER. Parte de la clave del upsert. |
external_id | string | Sí | Identificador del operador en el sistema externo. Parte de la clave del upsert. |
name | string | Sí | Nombre del operador. |
email | string | No | Correo electrónico. Debe ser un correo válido y no puede estar en uso por otro operador. |
phone | string | No | Número de teléfono. |
metadata | object | No | JSON arbitrario. Se fusiona recursivamente con la metadata existente. |
Ejemplo
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()Respuesta 200 OK
En este ejemplo, OP-001 ya existía y se actualizó, OP-002 se creó, y OP-003 falló porque su email ya está en uso por otro 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 de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
summary.total | number | Total de items enviados. |
summary.created | number | Items insertados (operadores nuevos). |
summary.updated | number | Items actualizados (operadores existentes modificados). |
summary.failed | number | Items que no se pudieron persistir. |
errors | object[] | Detalle por item fallido (vacío si todos tuvieron éxito). |
errors[].index | number | Posición (base 0) del item dentro del array items enviado. |
errors[].identifier | string | El external_id del item fallido. |
errors[].error | string | Código de error estable (ver tabla siguiente). |
errors[].message | string | Mensaje legible para humanos/logs con la causa. |
Códigos de error por item
error | Significado |
|---|---|
upsert_error | El item no se pudo persistir; message trae la causa (p. ej. un email ya en uso por otro operador). |
Errores (de la request completa)
| Status | Descripción |
|---|---|
400 | items vacío o con más de 500 elementos; un item que no cumple el formato (campo requerido ausente, tipo o valor de enum inválido); o una propiedad no declarada aquí. |
401 | Clave de API faltante o inválida. |
403 | La clave de API no tiene el permiso operator:batch-upsert. |