Reten Docs

Bulk Create Activities

Create multiple activities in a single request.

POST /api/activities/bulk

Create up to 200 activities in a single request. Each item follows the same schema as the single Create Activity endpoint.

Auth: Required — activity:create permission

Request Body

FieldTypeRequiredDescription
itemsarrayYesArray of activity objects (min 1, max 200)

Each item in items has the same fields as the Create Activity request body:

FieldTypeRequiredDescription
typestringYesTASK
reasonstringYesReason code: lowercase letters, digits and _ only (^[a-z0-9_]+$), max 100 characters. A code that is not in the tenant's reason catalog is added to it on the fly
channelstringYesSALESMAN, CALLCENTER
commerce_idUUIDConditionalTarget commerce ID. Send it or commerce_external_id: at least one is required. If you send both, commerce_id wins
commerce_external_idstringConditionalTarget commerce by external ID (max 255 characters). If no commerce has it, the API creates a placeholder commerce with that external ID
idempotency_keystringYesUnique key per tenant (prevents duplicates)
scheduled_atISO 8601YesWhen the activity should be executed
intent_categorystringNoONBOARDING, MARKETING, COLLECTION, RETENTION, NOTIFICATION, SURVEY or VALIDATION. Default NOTIFICATION
suggested_execution_timeISO 8601NoSuggested time for execution
user_statusstringNoUser status context
scoreintegerNoPriority score
assignment_reasonstringNoReason for the assignment
activity_detailsobjectYes, for TASKRoute and operator assignment (see Create Activity). A TASK item without it fails with validation_error

Example

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

Response 201 Created

Once the body passes validation, the API always responds 201 Created, even if every item fails. Items are processed one at a time, in order, and a failed item does not affect the others. The body reports the outcome of each item; index is the item's 0-based position in items.

FieldTypeDescription
summaryobjectCounters: total (items received), created, skipped and failed
itemsarrayCreated activities: index, idempotencyKey, activityId
skippedarrayItems whose idempotency_key already belongs to an activity of the active project: index, idempotencyKey, existingActivityId. Nothing is created or changed, whatever the status of that activity
errorsarrayFailed items: index, idempotencyKey, error, message

error is one of these codes, and message is the API's message for that item:

errorWhenExample message
validation_errorThe item passes body validation but breaks a creation ruleSe 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_foundcommerce_id, assigned_route_id or assigned_operator_id does not existCommerce not found, Route not found, Operator not found
creation_errorAny other failure — for example, the idempotency_key is already used by an activity of another project of the tenantLa clave de idempotencia ya está en uso.

An idempotency_key already used in another project is reported as a creation_error item, never as an HTTP 409.

Example for a batch of three items: the first is created, the second has an idempotency_key that already exists in the project, and the third references a commerce that does not exist.

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

Error Responses

StatusDescription
400Invalid body: items is empty or has more than 200 elements, or an item has an invalid or missing field, or a field the endpoint does not accept. Nothing is created
401Missing or invalid authentication token
403Missing activity:create permission