Reten Docs

Batch Upsert

Create or update up to 500 operators in a single operation, with per-item result reporting.

POST /api/integration/operators/batch-upsert

Creates or updates (upsert) up to 500 operators in a single operation. It is the integration entry point for loading the operator roster (salesmen, activators, call center agents) from an external system.

Auth: Required — operator:batch-upsert permission

Partial success. Once the body passes format validation, each item is processed independently: the ones that can be persisted are, and the ones that fail are reported in errors[] with a stable code — a failing item does not undo the others. Always check summary.failed and errors in the response.

Upsert key

Each item is matched against existing operators by the pair type + external_id. If an operator with that pair exists it is updated (counted in summary.updated); otherwise it is created (summary.created). The same external_id under a different type is a different operator. On update, metadata is merged with the existing one: keys sent overwrite keys with the same name, keys not sent are kept, and sending a key with null removes it. The merge is recursive (RFC 7386 JSON Merge Patch): nested objects are merged level by level, so you can update one key inside a nested object without resending its siblings. Any other value replaces the previous one wholesale — arrays included, which are never merged element by element. Sending metadata: null empties the whole bag.

Request Body

FieldTypeRequiredDescription
itemsobject[]YesList of operators to write (minimum 1, maximum 500).

Each element of items:

FieldTypeRequiredDescription
typeenumYesOperator type: SALESMAN, ACTIVATOR or CALLCENTER. Part of the upsert key.
external_idstringYesIdentifier of the operator in the external system. Part of the upsert key.
namestringYesOperator name.
emailstringNoEmail address. Must be a valid email and must not be in use by another operator.
phonestringNoPhone number.
metadataobjectNoArbitrary JSON. Merged recursively with the existing metadata.

Example

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()

Response 200 OK

In this example, OP-001 already existed and was updated, OP-002 was created, and OP-003 failed because its email is already used by another operator:

{
  "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"
    }
  ]
}

Response fields

FieldTypeDescription
summary.totalnumberTotal items submitted.
summary.creatednumberItems inserted (new operators).
summary.updatednumberItems updated (existing operators modified).
summary.failednumberItems that could not be persisted.
errorsobject[]Detail per failed item (empty if all succeeded).
errors[].indexnumberPosition (0-based) of the item within the submitted items array.
errors[].identifierstringThe external_id of the failed item.
errors[].errorstringStable error code (see table below).
errors[].messagestringHuman/log-readable message with the cause.

Per-item error codes

errorMeaning
upsert_errorThe item could not be persisted; message carries the cause (e.g. an email already used by another operator).

Errors (for the whole request)

StatusDescription
400items empty or with more than 500 elements; an item that does not match the format (missing required field, invalid type or enum value); or a property not declared here.
401Missing or invalid API key.
403The API key lacks the operator:batch-upsert permission.