Crear Actividades en Lote
Crear múltiples actividades en una sola solicitud.
POST /api/activities/bulk
Crea hasta 200 actividades en una sola solicitud. Cada elemento sigue el mismo esquema que el endpoint individual Crear Actividad.
Auth: Requerida — permiso activity:create
Cuerpo de la Solicitud
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
items | array | Sí | Array de objetos de actividad (min 1, max 200) |
Cada elemento en items tiene los mismos campos que el cuerpo de la solicitud de Crear Actividad:
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
type | string | Sí | TASK |
reason | string | Sí | Código de razón: solo minúsculas, dígitos y _ (^[a-z0-9_]+$), máximo 100 caracteres. Un código que no está en el catálogo de razones del tenant se agrega al vuelo |
channel | string | Sí | SALESMAN, CALLCENTER |
commerce_id | UUID | Condicional | ID del comercio destino. Envía este campo o commerce_external_id: al menos uno es obligatorio. Si envías los dos, gana commerce_id |
commerce_external_id | string | Condicional | Comercio destino por ID externo (máximo 255 caracteres). Si ningún comercio lo tiene, la API crea un comercio placeholder con ese ID externo |
idempotency_key | string | Sí | Clave única por tenant (previene duplicados) |
scheduled_at | ISO 8601 | Sí | Cuándo se debe ejecutar la actividad |
intent_category | string | No | ONBOARDING, MARKETING, COLLECTION, RETENTION, NOTIFICATION, SURVEY o VALIDATION. Por defecto NOTIFICATION |
suggested_execution_time | ISO 8601 | No | Hora sugerida de ejecución |
user_status | string | No | Contexto de estado del usuario |
score | integer | No | Puntaje de prioridad |
assignment_reason | string | No | Razón de la asignación |
activity_details | object | Sí, para TASK | Asignación de ruta y operador (ver Crear Actividad). Un elemento TASK sin este campo falla con validation_error |
Ejemplo
curl -X POST https://api.reten.ai/api/activities/bulk \
-H "Authorization: Bearer <token>" \
-H "x-tenant-id: <tenant-id>" \
-H "x-project-id: <project-id>" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"type": "TASK",
"reason": "monthly_visit",
"channel": "SALESMAN",
"commerce_id": "880e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "task-2025-01-15-001",
"scheduled_at": "2025-01-16T09:00:00.000Z",
"activity_details": {
"assigned_route_id": "dd0e8400-e29b-41d4-a716-446655440000",
"assigned_operator_id": "cc0e8400-e29b-41d4-a716-446655440000"
}
},
{
"type": "TASK",
"reason": "monthly_visit",
"channel": "CALLCENTER",
"commerce_external_id": "SHOP-0042",
"idempotency_key": "task-2025-01-15-002",
"scheduled_at": "2025-01-16T09:00:00.000Z",
"activity_details": {
"assigned_route_external_id": "ROUTE-07",
"assigned_operator_external_id": "AGENT-15"
}
}
]
}'const response = await fetch("https://api.reten.ai/api/activities/bulk", {
method: "POST",
headers: {
"Authorization": "Bearer <token>",
"x-tenant-id": "<tenant-id>",
"x-project-id": "<project-id>",
"Content-Type": "application/json",
},
body: JSON.stringify({
items: [
{
type: "TASK",
reason: "monthly_visit",
channel: "SALESMAN",
commerce_id: "880e8400-e29b-41d4-a716-446655440000",
idempotency_key: "task-2025-01-15-001",
scheduled_at: "2025-01-16T09:00:00.000Z",
activity_details: {
assigned_route_id: "dd0e8400-e29b-41d4-a716-446655440000",
assigned_operator_id: "cc0e8400-e29b-41d4-a716-446655440000",
},
},
{
type: "TASK",
reason: "monthly_visit",
channel: "CALLCENTER",
commerce_external_id: "SHOP-0042",
idempotency_key: "task-2025-01-15-002",
scheduled_at: "2025-01-16T09:00:00.000Z",
activity_details: {
assigned_route_external_id: "ROUTE-07",
assigned_operator_external_id: "AGENT-15",
},
},
],
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.reten.ai/api/activities/bulk",
headers={
"Authorization": "Bearer <token>",
"x-tenant-id": "<tenant-id>",
"x-project-id": "<project-id>",
},
json={
"items": [
{
"type": "TASK",
"reason": "monthly_visit",
"channel": "SALESMAN",
"commerce_id": "880e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "task-2025-01-15-001",
"scheduled_at": "2025-01-16T09:00:00.000Z",
"activity_details": {
"assigned_route_id": "dd0e8400-e29b-41d4-a716-446655440000",
"assigned_operator_id": "cc0e8400-e29b-41d4-a716-446655440000",
},
},
{
"type": "TASK",
"reason": "monthly_visit",
"channel": "CALLCENTER",
"commerce_external_id": "SHOP-0042",
"idempotency_key": "task-2025-01-15-002",
"scheduled_at": "2025-01-16T09:00:00.000Z",
"activity_details": {
"assigned_route_external_id": "ROUTE-07",
"assigned_operator_external_id": "AGENT-15",
},
},
],
},
)
result = response.json()Respuesta 201 Created
Una vez que el cuerpo pasa la validación, la API responde siempre 201 Created, aunque fallen todos los elementos. Los elementos se procesan de a uno, en orden, y un elemento que falla no afecta a los demás. El cuerpo informa el resultado de cada elemento; index es la posición del elemento en items, contando desde 0.
| Campo | Tipo | Descripción |
|---|---|---|
summary | object | Contadores: total (elementos recibidos), created, skipped y failed |
items | array | Actividades creadas: index, idempotencyKey, activityId |
skipped | array | Elementos cuya idempotency_key ya pertenece a una actividad del proyecto activo: index, idempotencyKey, existingActivityId. No se crea ni se modifica nada, sea cual sea el estado de esa actividad |
errors | array | Elementos que fallaron: index, idempotencyKey, error, message |
error es uno de estos códigos, y message es el mensaje de la API para ese elemento:
error | Cuándo | Ejemplo de message |
|---|---|---|
validation_error | El elemento pasa la validación del cuerpo pero incumple una regla de creación | Se requiere al menos uno de los campos commerce_id o commerce_external_id., TASK activities require activityDetails, assigned_route_id is required for TASK activities |
not_found | commerce_id, assigned_route_id o assigned_operator_id no existe | Commerce not found, Route not found, Operator not found |
creation_error | Cualquier otra falla — por ejemplo, la idempotency_key ya la usa una actividad de otro proyecto del tenant | La clave de idempotencia ya está en uso. |
Una idempotency_key ya usada en otro proyecto se informa como un elemento creation_error, nunca como un HTTP 409.
Ejemplo para un lote de tres elementos: el primero se crea, el segundo tiene una idempotency_key que ya existe en el proyecto y el tercero referencia un comercio que no existe.
{
"summary": {
"total": 3,
"created": 1,
"skipped": 1,
"failed": 1
},
"items": [
{
"index": 0,
"idempotencyKey": "task-2025-01-15-001",
"activityId": "ee0e8400-e29b-41d4-a716-446655440000"
}
],
"skipped": [
{
"index": 1,
"idempotencyKey": "task-2025-01-14-007",
"existingActivityId": "ff0e8400-e29b-41d4-a716-446655440000"
}
],
"errors": [
{
"index": 2,
"idempotencyKey": "task-2025-01-15-003",
"error": "not_found",
"message": "Commerce not found"
}
]
}Respuestas de Error
| Estado | Descripción |
|---|---|
400 | Cuerpo inválido: items está vacío o tiene más de 200 elementos, o un elemento tiene un campo inválido o faltante, o un campo que el endpoint no acepta. No se crea nada |
401 | Token de autenticación faltante o inválido |
403 | Falta el permiso activity:create |